2# ifndef _CRT_SECURE_NO_WARNINGS
3# define _CRT_SECURE_NO_WARNINGS
29std::size_t
strcmp(
const std::string& s,
const std::string& e) {
32std::size_t
strcmp(
const std::string& s,
const char* e) {
35std::size_t
strcmp(
const char* e,
const std::string& s) {
40void strcpy(std::string& s,
const std::string& e) {
51static thread_local shared_ptr<CoolProp::HelmholtzEOSBackend> Water;
52static thread_local shared_ptr<CoolProp::HelmholtzEOSBackend> Air;
53static thread_local shared_ptr<CoolProp::AbstractState> WaterIF97;
86#if !defined(NO_FMTLIB) && FMT_VERSION >= 90000
87int format_as(
givens given) {
88 return fmt::underlying(given);
92void _HAPropsSI_inputs(
double p,
const std::vector<givens>& input_keys,
const std::vector<double>& input_vals,
double&
T,
double& psi_w);
104 Water = std::make_shared<CoolProp::HelmholtzEOSBackend>(
"Water");
106 Air = std::make_shared<CoolProp::HelmholtzEOSBackend>(
"Air");
110static double epsilon = 0.621945, R_bar = 8.314472;
114static std::atomic<int> FlagUseVirialCorrelations{0};
115static std::atomic<int> FlagUseIsothermCompressCorrelation{0};
116static std::atomic<int> FlagUseIdealGasEnthalpyCorrelations{0};
120static inline bool check_bounds(
const givens prop,
const double& value,
double& min_val,
double& max_val) {
135 min_val = -143.15 + 273.15;
136 max_val = 350 + 273.15;
155 bool ret = !((value < min_val) || (value > max_val));
160static double MM_Air() {
164static double MM_Water() {
175static thread_local struct
180} s_virial_cache = {-1.0, 0, 0, 0, 0, 0, 0, 0, 0};
191 double tau = red.
T /
T;
192 double dtau_dT = -red.
T / (
T *
T);
200static void fill_virial_cache(
double T) {
201 if (
T == s_virial_cache.T)
return;
203 calc_all_virials(Air.get(),
T, s_virial_cache.B_a, s_virial_cache.dBdT_a, s_virial_cache.C_a, s_virial_cache.dCdT_a);
204 calc_all_virials(Water.get(),
T, s_virial_cache.B_w, s_virial_cache.dBdT_w, s_virial_cache.C_w, s_virial_cache.dCdT_w);
205 s_virial_cache.T =
T;
210static thread_local struct
215} s_alpha0_cache = {-1.0, 0, 0, 0, 0};
228 if (comps.size() != 1) {
232 double tau = red.
T /
T;
238 da0_dtau = derivs.dalphar_dtau;
241static void fill_alpha0_cache(
double T) {
242 if (
T == s_alpha0_cache.T)
return;
244 calc_ideal_gas_alpha0(Air.get(),
T, s_alpha0_cache.a0_a, s_alpha0_cache.da0_dtau_a);
245 calc_ideal_gas_alpha0(Water.get(),
T, s_alpha0_cache.a0_w, s_alpha0_cache.da0_dtau_w);
246 s_alpha0_cache.T =
T;
255static thread_local struct
265} s_ref = {
false, 0, 0, 0, 0, 0, 0, 0, 0};
267static void ensure_ref_offsets() {
268 if (s_ref.ready)
return;
270 const double R_bar_global = 8.314472;
271 const double R_bar_ws = 8.314371;
272 const double R_bar_air = 8.314510;
281 const double Tref = 473.15, vmolarref = 0.038837428192186184, href = 51885.582451893446;
284 Water->unspecify_phase();
285 double tauref = s_ref.T_red_w / Tref;
287 s_ref.hoffset_w = href - href_EOS;
292 const double Tref = 473.15, pref = 101325, sref = 141.18297895840303;
295 Water->unspecify_phase();
296 double tauref = s_ref.T_red_w / Tref;
298 s_ref.soffset_w = sref - sref_EOS;
303 const double Tref = 473.15, vmolarref = 0.038837428192186184, href = 13782.240592933371;
306 Air->unspecify_phase();
307 double tauref = 132.6312 / Tref;
309 s_ref.hoffset_a = href - href_EOS;
314 const double T0 = 273.15, p0 = 101325;
315 const double Tref = 473.15, vmolarref = 0.038837605637863169, sref = 212.22365283759311;
316 double vmolar_a_0 = R_bar_air * T0 / p0;
319 Air->unspecify_phase();
320 double tauref = 132.6312 / Tref;
322 + R_bar_air * log(vmolarref / vmolar_a_0);
323 s_ref.soffset_a = sref - sref_EOS;
325 s_ref.ln_delta_air_s = log(1.0 / (vmolar_a_0 * s_ref.rho_red_a));
331static double B_Air(
double T) {
332 fill_virial_cache(
T);
333 return s_virial_cache.B_a;
335static double dBdT_Air(
double T) {
336 fill_virial_cache(
T);
337 return s_virial_cache.dBdT_a;
339static double B_Water(
double T) {
340 fill_virial_cache(
T);
341 return s_virial_cache.B_w;
343static double dBdT_Water(
double T) {
344 fill_virial_cache(
T);
345 return s_virial_cache.dBdT_w;
347static double C_Air(
double T) {
348 fill_virial_cache(
T);
349 return s_virial_cache.C_a;
351static double dCdT_Air(
double T) {
352 fill_virial_cache(
T);
353 return s_virial_cache.dCdT_a;
355static double C_Water(
double T) {
356 fill_virial_cache(
T);
357 return s_virial_cache.C_w;
359static double dCdT_Water(
double T) {
360 fill_virial_cache(
T);
361 return s_virial_cache.dCdT_w;
364 if (flag == 0 || flag == 1) {
365 FlagUseVirialCorrelations = flag;
367 std::cout <<
"UseVirialCorrelations takes an integer, either 0 (no) or 1 (yes)\n";
371 if (flag == 0 || flag == 1) {
372 FlagUseIsothermCompressCorrelation = flag;
374 std::cout <<
"UseIsothermCompressCorrelation takes an integer, either 0 (no) or 1 (yes)\n";
378 if (flag == 0 || flag == 1) {
379 FlagUseIdealGasEnthalpyCorrelations = flag;
381 std::cout <<
"UseIdealGasEnthalpyCorrelations takes an integer, either 0 (no) or 1 (yes)\n";
384static double Brent_HAProps_W(
givens OutputKey,
double p,
givens In1Name,
double Input1,
double TargetVal,
double W_min,
double W_max) {
393 double Input1, TargetVal;
394 std::vector<givens> input_keys;
395 std::vector<double> input_vals;
398 BrentSolverResids(
givens OutputKey,
double p,
givens In1Key,
double Input1,
double TargetVal)
399 : OutputKey(OutputKey), p(p), In1Key(In1Key), Input1(Input1), TargetVal(TargetVal) {
400 input_keys.resize(2);
401 input_keys[0] = In1Key;
403 input_vals.resize(2);
404 input_vals[0] = Input1;
407 double call(
double W)
override {
409 double T = _HUGE, psi_w = _HUGE;
412 std::cout <<
format(
"T: %g K, psi_w %g\n",
T, psi_w);
418 BrentSolverResids BSR = BrentSolverResids(OutputKey, p, In1Name, Input1, TargetVal);
422 double r_min = BSR.call(W_min);
424 double r_max = BSR.call(W_max);
426 if (!W_min_valid && !W_max_valid) {
427 throw CoolProp::ValueError(
format(
"Both W_min [%g] and W_max [%g] yield invalid output values in Brent_HAProps_W", W_min, W_max).c_str());
428 }
else if (W_min_valid && !W_max_valid) {
429 while (!W_max_valid) {
431 W_max = 0.95 * W_max + 0.05 * W_min;
432 r_max = BSR.call(W_max);
435 }
else if (!W_min_valid && W_max_valid) {
436 while (!W_min_valid) {
438 W_min = 0.95 * W_min + 0.05 * W_max;
439 r_min = BSR.call(W_min);
444 if (r_min * r_max > 0) {
445 if (std::abs(r_min) < std::abs(r_max)) {
457 double r_check =
ValidNumber(W) ? BSR.call(W) : _HUGE;
458 if (!
ValidNumber(r_check) || std::abs(r_check) > 1e-4 * std::abs(TargetVal) + 1e-6) {
460 format(
"Brent_HAProps_W: no humidity ratio in [%g, %g] yields the requested output [%g] (closest residual %g); the input is out of range",
461 W_min, W_max, TargetVal, r_check)
466static double Brent_HAProps_T(
givens OutputKey,
double p,
givens In1Name,
double Input1,
double TargetVal,
double T_min,
double T_max) {
474 double Input1, TargetVal;
475 std::vector<givens> input_keys;
476 std::vector<double> input_vals;
479 BrentSolverResids(
givens OutputKey,
double p,
givens In1Key,
double Input1,
double TargetVal)
480 : OutputKey(OutputKey), p(p), In1Key(In1Key), Input1(Input1), TargetVal(TargetVal) {
481 input_keys.resize(2);
482 input_keys[0] = In1Key;
484 input_vals.resize(2);
485 input_vals[0] = Input1;
488 double call(
double T_drybulb)
override {
492 return val - TargetVal;
496 BrentSolverResids BSR = BrentSolverResids(OutputKey, p, In1Name, Input1, TargetVal);
500 double r_min = BSR.call(T_min);
502 double r_max = BSR.call(T_max);
504 if (!T_min_valid && !T_max_valid) {
505 throw CoolProp::ValueError(
format(
"Both T_min [%g] and T_max [%g] yield invalid output values in Brent_HAProps_T", T_min, T_max).c_str());
506 }
else if (T_min_valid && !T_max_valid) {
507 while (!T_max_valid) {
509 T_max = 0.95 * T_max + 0.05 * T_min;
510 r_max = BSR.call(T_max);
513 }
else if (!T_min_valid && T_max_valid) {
514 while (!T_min_valid) {
516 T_min = 0.95 * T_min + 0.05 * T_max;
517 r_min = BSR.call(T_min);
522 if (r_min * r_max > 0) {
523 if (std::abs(r_min) < std::abs(r_max)) {
529 double mach_eps = 1e-15, tol = 1e-10;
538 if (!
ValidNumber(r_check) || std::abs(r_check) > 1e-4 * std::abs(TargetVal) + 1e-6) {
540 format(
"Brent_HAProps_T: no temperature in [%g, %g] K yields the requested output [%g] (closest residual %g); the input is out of range",
541 T_min, T_max, TargetVal, r_check)
546static double Secant_Tdb_at_saturated_W(
double psi_w,
double p,
double T_guess) {
551 double pp_water, psi_w, p;
554 BrentSolverResids(
double psi_w,
double p)
555 : pp_water(psi_w * p), psi_w(psi_w), p(p) {
558 ~BrentSolverResids() =
default;
560 double call(
double T)
override {
564 p_ws = IF97::psat97(
T);
570 double pp_water_calc = f * p_ws;
571 double psi_w_calc = pp_water_calc / p;
572 return (psi_w_calc - psi_w) / psi_w;
576 BrentSolverResids Resids(psi_w, p);
583 }
catch (std::exception& ) {
654static double _B_aw(
double T) {
657 double a[] = {0, 0.665687e2, -0.238834e3, -0.176755e3};
658 double b[] = {0, -0.237, -1.048, -3.183};
659 double rhobarstar = 1000, Tstar = 100;
660 return 1 / rhobarstar * (a[1] * pow(
T / Tstar, b[1]) + a[2] * pow(
T / Tstar, b[2]) + a[3] * pow(
T / Tstar, b[3]))
664static double _dB_aw_dT(
double T) {
667 double a[] = {0, 0.665687e2, -0.238834e3, -0.176755e3};
668 double b[] = {0, -0.237, -1.048, -3.183};
669 double rhobarstar = 1000, Tstar = 100;
670 return 1 / rhobarstar / Tstar
671 * (a[1] * b[1] * pow(
T / Tstar, b[1] - 1) + a[2] * b[2] * pow(
T / Tstar, b[2] - 1) + a[3] * b[3] * pow(
T / Tstar, b[3] - 1))
675static double _C_aaw(
double T) {
678 double c[] = {0, 0.482737e3, 0.105678e6, -0.656394e8, 0.294442e11, -0.319317e13};
679 double rhobarstar = 1000, Tstar = 1, summer = 0;
681 for (i = 1; i <= 5; i++) {
682 summer += c[i] * pow(
T / Tstar, 1 - i);
684 return 1.0 / rhobarstar / rhobarstar * summer / 1e6;
687static double _dC_aaw_dT(
double T) {
690 double c[] = {0, 0.482737e3, 0.105678e6, -0.656394e8, 0.294442e11, -0.319317e13};
691 double rhobarstar = 1000, Tstar = 1, summer = 0;
693 for (i = 2; i <= 5; i++) {
694 summer += c[i] * (1 - i) * pow(
T / Tstar, -i);
696 return 1.0 / rhobarstar / rhobarstar / Tstar * summer / 1e6;
699static double _C_aww(
double T) {
702 double d[] = {0, -0.1072887e2, 0.347804e4, -0.383383e6, 0.334060e8};
703 double rhobarstar = 1, Tstar = 1, summer = 0;
705 for (i = 1; i <= 4; i++) {
706 summer += d[i] * pow(
T / Tstar, 1 - i);
708 return -1.0 / rhobarstar / rhobarstar * exp(summer) / 1e6;
711static double _dC_aww_dT(
double T) {
714 double d[] = {0, -0.1072887e2, 0.347804e4, -0.383383e6, 0.334060e8};
715 double rhobarstar = 1, Tstar = 1, summer1 = 0, summer2 = 0;
717 for (i = 1; i <= 4; i++) {
718 summer1 += d[i] * pow(
T / Tstar, 1 - i);
720 for (i = 2; i <= 4; i++) {
721 summer2 += d[i] * (1 - i) * pow(
T / Tstar, -i);
723 return -1.0 / rhobarstar / rhobarstar / Tstar * exp(summer1) * summer2
727static double B_m(
double T,
double psi_w) {
729 double B_aa = NAN, B_ww = NAN, B_aw = NAN;
730 if (FlagUseVirialCorrelations == 1) {
731 B_aa = -0.000721183853646 + 1.142682674467e-05 *
T - 8.838228412173e-08 * pow(
T, 2) + 4.104150642775e-10 * pow(
T, 3)
732 - 1.192780880645e-12 * pow(
T, 4) + 2.134201312070e-15 * pow(
T, 5) - 2.157430412913e-18 * pow(
T, 6) + 9.453830907795e-22 * pow(
T, 7);
733 B_ww = -10.8963128394 + 2.439761625859e-01 *
T - 2.353884845100e-03 * pow(
T, 2) + 1.265864734412e-05 * pow(
T, 3)
734 - 4.092175700300e-08 * pow(
T, 4) + 7.943925411344e-11 * pow(
T, 5) - 8.567808759123e-14 * pow(
T, 6) + 3.958203548563e-17 * pow(
T, 7);
741 return pow(1 - psi_w, 2) * B_aa + 2 * (1 - psi_w) * psi_w * B_aw + psi_w * psi_w * B_ww;
744static double dB_m_dT(
double T,
double psi_w) {
746 double dB_dT_aa = NAN, dB_dT_ww = NAN, dB_dT_aw = NAN;
747 if (FlagUseVirialCorrelations) {
748 dB_dT_aa = 1.65159324353e-05 - 3.026130954749e-07 *
T + 2.558323847166e-09 * pow(
T, 2) - 1.250695660784e-11 * pow(
T, 3)
749 + 3.759401946106e-14 * pow(
T, 4) - 6.889086380822e-17 * pow(
T, 5) + 7.089457032972e-20 * pow(
T, 6)
750 - 3.149942145971e-23 * pow(
T, 7);
751 dB_dT_ww = 0.65615868848 - 1.487953162679e-02 *
T + 1.450134660689e-04 * pow(
T, 2) - 7.863187630094e-07 * pow(
T, 3)
752 + 2.559556607010e-09 * pow(
T, 4) - 4.997942221914e-12 * pow(
T, 5) + 5.417678681513e-15 * pow(
T, 6)
753 - 2.513856275241e-18 * pow(
T, 7);
755 dB_dT_aa = dBdT_Air(
T);
756 dB_dT_ww = dBdT_Water(
T);
758 dB_dT_aw = _dB_aw_dT(
T);
759 return pow(1 - psi_w, 2) * dB_dT_aa + 2 * (1 - psi_w) * psi_w * dB_dT_aw + psi_w * psi_w * dB_dT_ww;
762static double C_m(
double T,
double psi_w) {
764 double C_aaa = NAN, C_www = NAN, C_aww = NAN, C_aaw = NAN;
765 if (FlagUseVirialCorrelations) {
766 C_aaa = 1.29192158975e-08 - 1.776054020409e-10 *
T + 1.359641176409e-12 * pow(
T, 2) - 6.234878717893e-15 * pow(
T, 3)
767 + 1.791668730770e-17 * pow(
T, 4) - 3.175283581294e-20 * pow(
T, 5) + 3.184306136120e-23 * pow(
T, 6) - 1.386043640106e-26 * pow(
T, 7);
768 C_www = -0.580595811134 + 1.365952762696e-02 *
T - 1.375986293288e-04 * pow(
T, 2) + 7.687692259692e-07 * pow(
T, 3)
769 - 2.571440816920e-09 * pow(
T, 4) + 5.147432221082e-12 * pow(
T, 5) - 5.708156494894e-15 * pow(
T, 6) + 2.704605721778e-18 * pow(
T, 7);
776 return pow(1 - psi_w, 3) * C_aaa + 3 * pow(1 - psi_w, 2) * psi_w * C_aaw + 3 * (1 - psi_w) * psi_w * psi_w * C_aww + pow(psi_w, 3) * C_www;
779static double dC_m_dT(
double T,
double psi_w) {
782 double dC_dT_aaa = NAN, dC_dT_www = NAN, dC_dT_aww = NAN, dC_dT_aaw = NAN;
784 if (FlagUseVirialCorrelations) {
785 dC_dT_aaa = -2.46582342273e-10 + 4.425401935447e-12 *
T - 3.669987371644e-14 * pow(
T, 2) + 1.765891183964e-16 * pow(
T, 3)
786 - 5.240097805744e-19 * pow(
T, 4) + 9.502177003614e-22 * pow(
T, 5) - 9.694252610339e-25 * pow(
T, 6)
787 + 4.276261986741e-28 * pow(
T, 7);
788 dC_dT_www = 0.0984601196142 - 2.356713397262e-03 *
T + 2.409113323685e-05 * pow(
T, 2) - 1.363083778715e-07 * pow(
T, 3)
789 + 4.609623799524e-10 * pow(
T, 4) - 9.316416405390e-13 * pow(
T, 5) + 1.041909136255e-15 * pow(
T, 6)
790 - 4.973918480607e-19 * pow(
T, 7);
792 dC_dT_aaa = dCdT_Air(
T);
793 dC_dT_www = dCdT_Water(
T);
795 dC_dT_aaw = _dC_aaw_dT(
T);
796 dC_dT_aww = _dC_aww_dT(
T);
797 return pow(1 - psi_w, 3) * dC_dT_aaa + 3 * pow(1 - psi_w, 2) * psi_w * dC_dT_aaw + 3 * (1 - psi_w) * psi_w * psi_w * dC_dT_aww
798 + pow(psi_w, 3) * dC_dT_www;
801 return psi_w * epsilon / (1 - psi_w);
804static double HenryConstant(
double T) {
806 double p_ws = NAN, beta_N2 = NAN, beta_O2 = NAN, beta_Ar = NAN, beta_a = NAN, tau = NAN, Tr = NAN, Tc = 647.096;
809 p_ws = IF97::psat97(
T);
810 beta_N2 = p_ws * exp(-9.67578 / Tr + 4.72162 * pow(tau, 0.355) / Tr + 11.70585 * pow(Tr, -0.41) * exp(tau));
811 beta_O2 = p_ws * exp(-9.44833 / Tr + 4.43822 * pow(tau, 0.355) / Tr + 11.42005 * pow(Tr, -0.41) * exp(tau));
812 beta_Ar = p_ws * exp(-8.40954 / Tr + 4.29587 * pow(tau, 0.355) / Tr + 10.52779 * pow(Tr, -0.41) * exp(tau));
813 beta_a = 1 / (0.7812 / beta_N2 + 0.2095 / beta_O2 + 0.0093 / beta_Ar);
814 return 1 / (1.01325 * beta_a);
820 if (FlagUseIsothermCompressCorrelation) {
821 k_T = 1.6261876614E-22 * pow(
T, 6) - 3.3016385196E-19 * pow(
T, 5) + 2.7978984577E-16 * pow(
T, 4) - 1.2672392901E-13 * pow(
T, 3)
822 + 3.2382864853E-11 * pow(
T, 2) - 4.4318979503E-09 *
T + 2.5455947289E-07;
832 constexpr double if97_sat_tol = 3.3e-5;
833 const double p_ws_T = IF97::psat97(
T);
834 if (std::abs(p - p_ws_T) <= p_ws_T * if97_sat_tol) {
835 k_T = 1.6261876614E-22 * pow(
T, 6) - 3.3016385196E-19 * pow(
T, 5) + 2.7978984577E-16 * pow(
T, 4) - 1.2672392901E-13 * pow(
T, 3)
836 + 3.2382864853E-11 * pow(
T, 2) - 4.4318979503E-09 *
T + 2.5455947289E-07;
849 double f = 0, Rbar = 8.314371, eps = 1e-8;
850 double x1 = 0, x2 = 0, x3 = NAN, y1 = 0, y2 = NAN, change = _HUGE;
852 double p_ws = NAN, B_aa = NAN, B_aw = NAN, B_ww = NAN, C_aaa = NAN, C_aaw = NAN, C_aww = NAN, C_www = NAN, line1 = NAN, line2 = NAN, line3 = NAN,
853 line4 = NAN, line5 = NAN, line6 = NAN, line7 = NAN, line8 = NAN, k_T = NAN, beta_H = NAN, LHS = NAN, RHS = NAN, psi_ws = NAN,
862 beta_H = HenryConstant(
T);
879 if (FlagUseVirialCorrelations) {
880 B_aa = -0.000721183853646 + 1.142682674467e-05 *
T - 8.838228412173e-08 * pow(
T, 2) + 4.104150642775e-10 * pow(
T, 3)
881 - 1.192780880645e-12 * pow(
T, 4) + 2.134201312070e-15 * pow(
T, 5) - 2.157430412913e-18 * pow(
T, 6) + 9.453830907795e-22 * pow(
T, 7);
882 B_ww = -10.8963128394 + 2.439761625859e-01 *
T - 2.353884845100e-03 * pow(
T, 2) + 1.265864734412e-05 * pow(
T, 3)
883 - 4.092175700300e-08 * pow(
T, 4) + 7.943925411344e-11 * pow(
T, 5) - 8.567808759123e-14 * pow(
T, 6) + 3.958203548563e-17 * pow(
T, 7);
884 C_aaa = 1.29192158975e-08 - 1.776054020409e-10 *
T + 1.359641176409e-12 * pow(
T, 2) - 6.234878717893e-15 * pow(
T, 3)
885 + 1.791668730770e-17 * pow(
T, 4) - 3.175283581294e-20 * pow(
T, 5) + 3.184306136120e-23 * pow(
T, 6) - 1.386043640106e-26 * pow(
T, 7);
886 C_www = -0.580595811134 + 1.365952762696e-02 *
T - 1.375986293288e-04 * pow(
T, 2) + 7.687692259692e-07 * pow(
T, 3)
887 - 2.571440816920e-09 * pow(
T, 4) + 5.147432221082e-12 * pow(
T, 5) - 5.708156494894e-15 * pow(
T, 6) + 2.704605721778e-18 * pow(
T, 7);
900 while ((iter <= 3 || change > eps) && iter < 100) {
906 x2 = 1.00 + 0.000001;
916 psi_ws = f * p_ws / p;
919 line1 = ((1 + k_T * p_ws) * (p - p_ws) - k_T * (p * p - p_ws * p_ws) / 2.0) / (Rbar *
T) * vbar_ws + log(1 - beta_H * (1 - psi_ws) * p);
920 line2 = pow(1 - psi_ws, 2) * p / (Rbar *
T) * B_aa - 2 * pow(1 - psi_ws, 2) * p / (Rbar *
T) * B_aw
921 - (p - p_ws - pow(1 - psi_ws, 2) * p) / (Rbar *
T) * B_ww;
922 line3 = pow(1 - psi_ws, 3) * p * p / pow(Rbar *
T, 2) * C_aaa
923 + (3 * pow(1 - psi_ws, 2) * (1 - 2 * (1 - psi_ws)) * p * p) / (2 * pow(Rbar *
T, 2)) * C_aaw;
924 line4 = -3 * pow(1 - psi_ws, 2) * psi_ws * p * p / pow(Rbar *
T, 2) * C_aww
925 - ((3 - 2 * psi_ws) * psi_ws * psi_ws * p * p - p_ws * p_ws) / (2 * pow(Rbar *
T, 2)) * C_www;
926 line5 = -(pow(1 - psi_ws, 2) * (-2 + 3 * psi_ws) * psi_ws * p * p) / pow(Rbar *
T, 2) * B_aa * B_ww;
927 line6 = -(2 * pow(1 - psi_ws, 3) * (-1 + 3 * psi_ws) * p * p) / pow(Rbar *
T, 2) * B_aa * B_aw;
928 line7 = (6 * pow(1 - psi_ws, 2) * psi_ws * psi_ws * p * p) / pow(Rbar *
T, 2) * B_ww * B_aw
929 - (3 * pow(1 - psi_ws, 4) * p * p) / (2 * pow(Rbar *
T, 2)) * B_aa * B_aa;
930 line8 = -(2 * pow(1 - psi_ws, 2) * psi_ws * (-2 + 3 * psi_ws) * p * p) / pow(Rbar *
T, 2) * B_aw * B_aw
931 - (p_ws * p_ws - (4 - 3 * psi_ws) * pow(psi_ws, 3) * p * p) / (2 * pow(Rbar *
T, 2)) * B_ww * B_ww;
932 RHS = line1 + line2 + line3 + line4 + line5 + line6 + line7 + line8;
939 x3 = x2 - y2 / (y2 - y1) * (x2 - x1);
940 change = std::abs(y2 / (y2 - y1) * (x2 - x1));
953 std::cout <<
"Sorry, Need to update!";
956 if (!
strcmp(Code,
"GIVEN_TDP"))
958 else if (!
strcmp(Code,
"GIVEN_HUMRAT"))
960 else if (!
strcmp(Code,
"GIVEN_TWB"))
962 else if (!
strcmp(Code,
"GIVEN_RH"))
964 else if (!
strcmp(Code,
"GIVEN_ENTHALPY"))
967 std::cerr <<
format(
"Code to returnHumAirCode in HumAir.c [%s] not understood", Code);
979 double mu_a = NAN, mu_w = NAN, Phi_av = NAN, Phi_va = NAN, Ma = NAN, Mw = NAN;
988 Phi_av = sqrt(2.0) / 4.0 * pow(1 + Ma / Mw, -0.5) * pow(1 + sqrt(mu_a / mu_w) * pow(Mw / Ma, 0.25), 2);
989 Phi_va = sqrt(2.0) / 4.0 * pow(1 + Mw / Ma, -0.5) * pow(1 + sqrt(mu_w / mu_a) * pow(Ma / Mw, 0.25), 2);
990 return (1 - psi_w) * mu_a / ((1 - psi_w) + psi_w * Phi_av) + psi_w * mu_w / (psi_w + (1 - psi_w) * Phi_va);
1000 double mu_a = NAN, mu_w = NAN, k_a = NAN, k_w = NAN, Phi_av = NAN, Phi_va = NAN, Ma = NAN, Mw = NAN;
1012 Phi_av = sqrt(2.0) / 4.0 * pow(1 + Ma / Mw, -0.5) * pow(1 + sqrt(mu_a / mu_w) * pow(Mw / Ma, 0.25), 2);
1013 Phi_va = sqrt(2.0) / 4.0 * pow(1 + Mw / Ma, -0.5) * pow(1 + sqrt(mu_w / mu_a) * pow(Ma / Mw, 0.25), 2);
1014 return (1 - psi_w) * k_a / ((1 - psi_w) + psi_w * Phi_av) + psi_w * k_w / (psi_w + (1 - psi_w) * Phi_va);
1025 double v_bar0 = NAN, v_bar = 0, R_bar = 8.314472, x1 = 0, x2 = 0, x3 = NAN, y1 = 0, y2 = NAN, resid = NAN, eps = NAN, Bm = NAN, Cm = NAN;
1032 v_bar0 = R_bar *
T / p;
1041 while ((iter <= 3 || std::abs(resid) > eps) && iter < 100) {
1047 x2 = v_bar0 + 0.000001;
1055 resid = (p - (R_bar)*
T / v_bar * (1 + Bm / v_bar + Cm / (v_bar * v_bar))) / p;
1062 x3 = x2 - y2 / (y2 - y1) * (x2 - x1);
1072 double R_bar = 8.314472;
1073 double Bm = B_m(
T, psi_w);
1074 double Cm = C_m(
T, psi_w);
1075 return (R_bar)*
T / v_bar * (1 + Bm / v_bar + Cm / (v_bar * v_bar));
1080 fill_alpha0_cache(
T);
1081 ensure_ref_offsets();
1082 double tau = s_ref.T_red_w /
T;
1083 return -0.01102303806 + s_ref.hoffset_w + R_bar *
T * (1.0 + tau * s_alpha0_cache.da0_dtau_w);
1088 const double R_bar_ws = 8.314371;
1089 fill_alpha0_cache(
T);
1090 ensure_ref_offsets();
1091 double tau = s_ref.T_red_w /
T;
1093 double ln_delta = log(p / (R_bar_ws *
T * s_ref.rho_red_w));
1094 return s_ref.soffset_w + R_bar_ws * (tau * s_alpha0_cache.da0_dtau_w - s_alpha0_cache.a0_w - ln_delta);
1097 const double R_bar_Lemmon = 8.314510;
1098 fill_alpha0_cache(
T);
1099 ensure_ref_offsets();
1100 double tau = 132.6312 /
T;
1101 return -7914.149298 + s_ref.hoffset_a + R_bar_Lemmon *
T * (1.0 + tau * s_alpha0_cache.da0_dtau_a);
1104 const double R_bar_Lemmon = 8.314510, T0 = 273.15, p0 = 101325;
1105 const double vmolar_a_0 = R_bar_Lemmon * T0 / p0;
1106 fill_alpha0_cache(
T);
1107 ensure_ref_offsets();
1108 double tau = 132.6312 /
T;
1110 return -196.1375815 + s_ref.soffset_a + R_bar_Lemmon * (tau * s_alpha0_cache.da0_dtau_a - s_alpha0_cache.a0_a - s_ref.ln_delta_air_s)
1111 + R_bar_Lemmon * log(vmolar_a / vmolar_a_0);
1126 double hbar_0 = NAN, hbar_a = NAN, hbar_w = NAN, hbar = NAN, R_bar = 8.314472;
1134 if (FlagUseIdealGasEnthalpyCorrelations) {
1135 hbar_w = 2.7030251618E-03 *
T *
T + 3.1994361015E+01 *
T + 3.6123174929E+04;
1136 hbar_a = 9.2486716590E-04 *
T *
T + 2.8557221776E+01 *
T - 7.8616129429E+03;
1146 hbar = hbar_0 + (1 - psi_w) * hbar_a + psi_w * hbar_w
1147 + R_bar *
T * ((B_m(
T, psi_w) -
T * dB_m_dT(
T, psi_w)) / vmolar + (C_m(
T, psi_w) -
T / 2.0 * dC_m_dT(
T, psi_w)) / (vmolar * vmolar));
1156 double M_ha = MM_Water() * psi_w + (1 - psi_w) * 0.028966;
1157 return h_bar / M_ha;
1163 double M_ha = MM_Water() * psi_w + (1 - psi_w) * 0.028966;
1164 return h_bar * (1 + W) / M_ha;
1169 double M_ha = MM_Water() * psi_w + (1 - psi_w) * 0.028966;
1170 return h_bar / M_ha;
1176 double M_ha = MM_Water() * psi_w + (1 - psi_w) * 0.028966;
1177 return h_bar * (1 + W) / M_ha;
1190 double x1 = 0, x2 = 0, x3 = 0, y1 = 0, y2 = 0, eps = 1e-8, f = 999, R_bar_Lem = 8.314510;
1192 double sbar_0 = NAN, sbar_a = 0, sbar_w = 0, sbar = NAN, R_bar = 8.314472, vbar_a_guess = NAN, Baa = NAN, Caaa = NAN, vbar_a = 0;
1193 double B = NAN, dBdT = NAN, C = NAN, dCdT = NAN;
1195 sbar_0 = 0.02366427495;
1201 dBdT = dB_m_dT(
T, psi_w);
1204 dCdT = dC_m_dT(
T, psi_w);
1206 vbar_a_guess = R_bar_Lem *
T / p;
1208 while ((iter <= 3 || std::abs(f) > eps) && iter < 100) {
1214 x2 = vbar_a_guess + 0.001;
1220 f = R_bar_Lem *
T / vbar_a * (1 + Baa / vbar_a + Caaa / pow(vbar_a, 2)) - p;
1226 x3 = x2 - y2 / (y2 - y1) * (x2 - x1);
1234 if (FlagUseIdealGasEnthalpyCorrelations) {
1235 std::cout <<
"Not implemented" <<
'\n';
1241 sbar = sbar_0 + (1 - psi_w) * sbar_a + psi_w * sbar_w
1242 - R_bar * ((B +
T * dBdT) / v_bar + (C +
T * dCdT) / (2 * pow(v_bar, 2)) + (1 - psi_w) * log(1 - psi_w) + psi_w * log(psi_w));
1244 sbar = sbar_0 + sbar_a - R_bar * ((B +
T * dBdT) / v_bar + (C +
T * dCdT) / (2 * pow(v_bar, 2)));
1252 double M_ha = MM_Water() * psi_w + (1 - psi_w) * 0.028966;
1253 return s_bar / M_ha;
1258 double M_ha = MM_Water() * psi_w + (1 - psi_w) * 0.028966;
1260 return s_bar * (1 + W) / M_ha;
1265 double p_w = NAN, eps = NAN, resid = NAN, Tdp = 0, x1 = 0, x2 = 0, x3 = NAN, y1 = 0, y2 = NAN, T0 = NAN;
1266 double p_ws_dp = NAN, f_dp = NAN;
1269 if ((1 - psi_w) < 1e-16) {
1282 if (p_w > 611.6547241637944) {
1283 T0 = IF97::Tsat97(p) - 1;
1293 while ((iter <= 3 || std::abs(resid) > eps) && iter < 100) {
1306 if (Tdp >= 273.16) {
1308 p_ws_dp = IF97::psat97(Tdp);
1316 resid = p_w - p_ws_dp * f_dp;
1323 x3 = x2 - y2 / (y2 - y1) * (x2 - x1);
1339 WetBulbSolver(
double T,
double p,
double psi_w) : _p(p), _W(epsilon * psi_w / (1 - psi_w)) {
1341 double v_bar_w =
MolarVolume(
T, p, psi_w), M_ha = MM_Water() * psi_w + (1 - psi_w) * 0.028966;
1345 double epsilon = 0.621945;
1346 double f_wb = NAN, p_ws_wb = NAN, p_s_wb = NAN, W_s_wb = NAN, h_w = NAN, M_ha_wb = NAN, psi_wb = NAN, v_bar_wb = NAN;
1352 p_ws_wb = IF97::psat97(Twb);
1359 p_s_wb = f_wb * p_ws_wb;
1361 W_s_wb = epsilon * p_s_wb / (_p - p_s_wb);
1363 psi_wb = W_s_wb / (epsilon + W_s_wb);
1372 h_w =
h_Ice(Twb, _p);
1376 M_ha_wb = MM_Water() * psi_wb + (1 - psi_wb) * 0.028966;
1378 double RHS = (
MolarEnthalpy(Twb, _p, psi_wb, v_bar_wb) * (1 + W_s_wb) / M_ha_wb + (_W - W_s_wb) * h_w);
1394 double psi_w = NAN,
T = Ts;
1396 std::vector<givens> inp(2);
1400 std::vector<double> val(2);
1425 double Tsat = IF97::Tsat97(p);
1446 double Tupper = std::min(Tmax + 1.0, Tsat - 0.1);
1448 double return_val = NAN;
1453 if (return_val > Tmax + 1) {
1461 if (
T >= Tsat && return_val > Tupper - 1e-2) {
1473 double Tmin =
Brent(WBTS, 210, Tsat - 1, 1e-12, 1e-12, 50);
1475 return_val =
Brent(WBS, Tmin - 30, Tmax - 1, 1e-12, 1e-12, 50);
1482static givens Name2Type(
const std::string& Name) {
1493 else if (!
strcmp(Name,
"Hha"))
1495 else if (!
strcmp(Name,
"InternalEnergy") || !
strcmp(Name,
"U") || !
strcmp(Name,
"Uda"))
1497 else if (!
strcmp(Name,
"Uha"))
1501 else if (!
strcmp(Name,
"Sha"))
1507 else if (!
strcmp(Name,
"P"))
1511 else if (!
strcmp(Name,
"Vha"))
1519 else if (!
strcmp(Name,
"Cha") || !
strcmp(Name,
"cp_ha"))
1521 else if (!
strcmp(Name,
"CV"))
1523 else if (!
strcmp(Name,
"CVha") || !
strcmp(Name,
"cv_ha"))
1525 else if (!
strcmp(Name,
"P_w"))
1527 else if (!
strcmp(Name,
"isentropic_exponent"))
1529 else if (!
strcmp(Name,
"speed_of_sound"))
1531 else if (!
strcmp(Name,
"Z"))
1535 "Sorry, your input [%s] was not understood to Name2Type. Acceptable values are T,P,R,W,D,B,H,S,M,K and aliases thereof\n", Name.c_str()));
1537int TypeMatch(
int TypeCode,
const std::string& Input1Name,
const std::string& Input2Name,
const std::string& Input3Name) {
1539 if (TypeCode == Name2Type(Input1Name))
return 1;
1540 if (TypeCode == Name2Type(Input2Name))
return 2;
1541 if (TypeCode == Name2Type(Input3Name))
1547 double p_ws = NAN, f = NAN, W = NAN, epsilon = 0.621945, Tdp = NAN, p_ws_dp = NAN, f_dp = NAN, p_w_dp = NAN, p_s = NAN, RH = NAN;
1552 return W / (epsilon + W);
1556 p_ws = IF97::psat97(
T);
1567 double psi_ws = p_s / p;
1573 if (Tdp >= 273.16) {
1574 p_ws_dp = IF97::psat97(Tdp);
1583 p_w_dp = f_dp * p_ws_dp;
1592 double p_ws = NAN, f = NAN, p_s = NAN;
1595 p_ws = IF97::psat97(
T);
1607 return psi_w * p / p_s;
1611 switch (Name2Type(Name)) {
1645 switch (Name2Type(Name)) {
1678double HAProps(
const std::string& OutputName,
const std::string& Input1Name,
double Input1,
const std::string& Input2Name,
double Input2,
1679 const std::string& Input3Name,
double Input3) {
1684 double out =
HAPropsSI(OutputName, Input1Name, Input1, Input2Name, Input2, Input3Name, Input3);
1691 if (input_keys.size() != 2) {
1695 if (input_keys[0] == key) {
1697 }
else if (input_keys[1] == key) {
1711 const double target;
1714 std::vector<double> input_vals;
1719 : p(p), target(target), output(output), _T(_HUGE), _psi_w(_HUGE) {
1720 input_vals.resize(2,
T);
1737 const double target;
1740 std::vector<double> input_vals;
1745 : p(p), target(target), output(output), _T(_HUGE), _psi_w(_HUGE) {
1746 input_vals.resize(2, W);
1760void _HAPropsSI_inputs(
double p,
const std::vector<givens>& input_keys,
const std::vector<double>& input_vals,
double&
T,
double& psi_w) {
1762 std::cout <<
format(
"length of input_keys is %d\n", input_keys.size());
1764 if (input_keys.size() != input_vals.size()) {
1765 throw CoolProp::ValueError(
format(
"Length of input_keys (%d) does not equal that of input_vals (%d)", input_keys.size(), input_vals.size()));
1771 long other = 1 - key;
1772 T = input_vals[key];
1774 std::cout <<
format(
"One of the inputs is T: %g K\n",
T);
1776 givens othergiven = input_keys[other];
1777 switch (othergiven) {
1782 std::cout <<
format(
"other input value is %g\n", input_vals[other]);
1783 std::cout <<
format(
"other input index is %d\n", othergiven);
1805 double MainInputValue =
T;
1807 double SecondaryInputValue = input_vals[other];
1808 givens SecondaryInputKey = input_keys[other];
1809 W = Brent_HAProps_W(SecondaryInputKey, p, MainInputKey, MainInputValue, SecondaryInputValue, W_min, W_max);
1820 std::cout <<
format(
"The main input is not T\n",
T);
1835 "Sorry, but currently at least one of the variables as an input to HAPropsSI() must be temperature, relative humidity, humidity ratio, "
1836 "or dewpoint\n Eventually will add a 2-D NR solver to find T and psi_w simultaneously, but not included now");
1844 int number_of_water_content_inputs =
1849 bool valid_two_water = has_rh && (has_tdp || has_humrat) && !(has_tdp && has_humrat);
1850 if (number_of_water_content_inputs > 1 && !valid_two_water) {
1852 "Sorry, but cannot provide two inputs that are both water-content (humidity ratio, relative humidity, absolute humidity");
1855 long other = 1 - key;
1858 double MainInputValue = input_vals[key];
1859 givens MainInputKey = input_keys[key];
1861 double SecondaryInputValue = input_vals[other];
1862 givens SecondaryInputKey = input_keys[other];
1865 std::cout <<
format(
"Main input is %g\n", MainInputValue);
1866 std::cout <<
format(
"Secondary input is %g\n", SecondaryInputValue);
1871 check_bounds(
GIVEN_T, 273.15, T_min, T_max);
1874 if (MainInputValue < 1e-10) {
1892 if (SecondaryInputKey ==
GIVEN_TWB && MainInputValue < 1e-3) {
1893 double T_wb_K = SecondaryInputValue;
1894 double psat_Twb = (T_wb_K > 273.16) ? IF97::psat97(T_wb_K) :
psub_Ice(T_wb_K);
1896 const double eps_W = 0.621945;
1897 double W_sat_wb = eps_W * psat_Twb / (p - psat_Twb);
1898 double hfg = 2.501e6 - 2400.0 * (T_wb_K - 273.15);
1899 double cp_a = 1006.0;
1900 double T_db_est = T_wb_K + W_sat_wb * hfg / cp_a;
1901 if (T_db_est > 500.0) {
1903 "balance implies T_db ~ %g K, beyond the validity range (~500 K) "
1904 "of the humid-air mixture model for very dry conditions.",
1905 T_wb_K, MainInputValue, p, T_db_est)
1908 T_max = std::max(T_max, T_db_est + 20.0);
1916 if (MainInputValue < 1e-10) {
1921 double T_dummy = -1,
1924 double pp_water_sat = psi_w_sat * p;
1927 if (pp_water_sat > Water->p_triple()) {
1928 T_min = IF97::Tsat97(pp_water_sat);
1933 T_min = Secant_Tdb_at_saturated_W(psi_w_sat, p, T_min);
1963 const bool twb_inverse = (SecondaryInputKey ==
GIVEN_TWB);
1964 const bool twb_near_triple = twb_inverse && (std::abs(SecondaryInputValue - 273.16) < 2.0);
1965 auto twb_triple_point_error = [&]() {
1966 const char* main_name = (MainInputKey ==
GIVEN_RH) ?
"RelHum"
1970 return CoolProp::ValueError(
format(
"HAPropsSI(T_wb=%g K, %s=%g, P=%g Pa): no dry-bulb temperature reproduces this wet-bulb. It lies "
1971 "in the unreachable band at the water triple point (273.16 K), where the ice/liquid latent-heat "
1972 "discontinuity makes Twb(T_db) skip a range of wet-bulb temperatures; the state is infeasible for "
1973 "these conditions.",
1974 SecondaryInputValue, main_name, MainInputValue, p)
1980 T = Brent_HAProps_T(SecondaryInputKey, p, MainInputKey, MainInputValue, SecondaryInputValue, T_min, T_max);
1981 }
catch (std::exception& e) {
1983 std::cout <<
"ERROR: " << e.what() <<
'\n';
1985 if (twb_near_triple) {
1986 throw twb_triple_point_error();
1995 std::vector<givens> input_keys(2,
GIVEN_T);
1996 input_keys[1] = MainInputKey;
1997 std::vector<double> input_vals(2,
T);
1998 input_vals[1] = MainInputValue;
2008 if (!
ValidNumber(wb_check) || std::abs(wb_check - SecondaryInputValue) > 1e-2) {
2009 throw twb_triple_point_error();
2016 std::cout <<
format(
"_HAPropsSI_outputs :: T: %g K, psi_w: %g\n",
T, psi_w);
2019 double M_ha = (1 - psi_w) * 0.028966 + MM_Water() * psi_w;
2023 switch (OutputType) {
2031 return v_bar * (1 + W) / M_ha;
2035 return v_bar / M_ha;
2084 double v_bar1 = NAN, v_bar2 = NAN, h_bar1 = NAN, h_bar2 = NAN, cp_ha = NAN, dT = 1e-3;
2089 cp_ha = (h_bar2 - h_bar1) / (2 * dT);
2090 return cp_ha / M_ha;
2097 double v_bar = NAN, p_1 = NAN, p_2 = NAN, u_bar1 = NAN, u_bar2 = NAN, cv_bar = NAN, dT = 1e-3;
2103 cv_bar = (u_bar2 - u_bar1) / (2 * dT);
2104 return cv_bar / M_ha;
2107 CoolPropDbl v_bar = NAN, dv = 1e-8, p_1 = NAN, p_2 = NAN;
2113 CoolPropDbl dpdv__constT = (p_2 - p_1) / (2 * dv);
2114 return -cp / cv * dpdv__constT * v_bar / p;
2117 CoolPropDbl v_bar = NAN, dv = 1e-8, p_1 = NAN, p_2 = NAN;
2124 CoolPropDbl dpdrho__constT = (p_2 - p_1) / (2 * dv) * dvdrho;
2125 return sqrt(1 / M_ha * cp / cv * dpdrho__constT);
2129 double R_u_molar = 8.314472;
2130 return p * v_bar / (R_u_molar *
T);
2136double HAPropsSI(
const std::string& OutputName,
const std::string& Input1Name,
double Input1,
const std::string& Input2Name,
double Input2,
2137 const std::string& Input3Name,
double Input3) {
2145 std::cout <<
format(
"HAPropsSI(%s,%s,%g,%s,%g,%s,%g)\n", OutputName.c_str(), Input1Name.c_str(), Input1, Input2Name.c_str(), Input2,
2146 Input3Name.c_str(), Input3);
2149 std::vector<givens> input_keys(2);
2150 std::vector<double> input_vals(2);
2152 givens In1Type, In2Type, In3Type, OutputType;
2153 double p = NAN,
T = _HUGE, psi_w = _HUGE;
2156 In1Type = Name2Type(Input1Name.c_str());
2157 In2Type = Name2Type(Input2Name.c_str());
2158 In3Type = Name2Type(Input3Name.c_str());
2161 OutputType = Name2Type(OutputName.c_str());
2164 if (OutputType == In1Type) {
2167 if (OutputType == In2Type) {
2170 if (OutputType == In3Type) {
2177 input_keys[0] = In2Type;
2178 input_keys[1] = In3Type;
2179 input_vals[0] = Input2;
2180 input_vals[1] = Input3;
2181 }
else if (In2Type ==
GIVEN_P) {
2183 input_keys[0] = In1Type;
2184 input_keys[1] = In3Type;
2185 input_vals[0] = Input1;
2186 input_vals[1] = Input3;
2187 }
else if (In3Type ==
GIVEN_P) {
2189 input_keys[0] = In1Type;
2190 input_keys[1] = In2Type;
2191 input_vals[0] = Input1;
2192 input_vals[1] = Input2;
2197 if (input_keys[0] == input_keys[1]) {
2198 throw CoolProp::ValueError(
"Other two inputs to HAPropsSI aside from pressure cannot be the same");
2202 double min_val = _HUGE, max_val = -_HUGE;
2203 for (std::size_t i = 0; i < input_keys.size(); i++) {
2204 if (!check_bounds(input_keys[i], input_vals[i], min_val, max_val)) {
2206 input_keys[i], input_vals[i], min_val, max_val));
2216 std::cout <<
format(
"HAPropsSI input conversion yields T: %g, psi_w: %g\n",
T, psi_w);
2220 if (!check_bounds(
GIVEN_P, p, min_val, max_val)) {
2221 throw CoolProp::ValueError(
format(
"The pressure value (%g) is outside the range of validity: (%g) to (%g)", p, min_val, max_val));
2226 if (!check_bounds(
GIVEN_T,
T, min_val, max_val)) {
2227 throw CoolProp::ValueError(
format(
"The temperature value (%g) is outside the range of validity: (%g) to (%g)",
T, min_val, max_val));
2232 if (!check_bounds(
GIVEN_PSIW, psi_w, min_val, max_val)) {
2234 format(
"The water mole fraction value (%g) is outside the range of validity: (%g) to (%g)", psi_w, min_val, max_val));
2242 if (!check_bounds(OutputType, val, min_val, max_val)) {
2244 format(
"The output for key (%d) with value (%g) is outside the range of validity: (%g) to (%g)", OutputType, val, min_val, max_val));
2252 std::cout <<
format(
"HAPropsSI is about to return invalid number");
2258 std::cout <<
format(
"HAPropsSI is about to return %g\n", val);
2261 }
catch (std::exception& e) {
2269double HAProps_Aux(
const char* Name,
double T,
double p,
double W,
char* units) {
2278 double psi_w = NAN, B_aa = NAN, C_aaa = NAN, B_ww = NAN, C_www = NAN, B_aw = NAN, C_aaw = NAN, C_aww = NAN, v_bar = NAN;
2281 if (!
strcmp(Name,
"Baa")) {
2283 strcpy(units,
"m^3/mol");
2285 }
else if (!
strcmp(Name,
"Caaa")) {
2287 strcpy(units,
"m^6/mol^2");
2289 }
else if (!
strcmp(Name,
"Bww")) {
2291 strcpy(units,
"m^3/mol");
2293 }
else if (!
strcmp(Name,
"Cwww")) {
2295 strcpy(units,
"m^6/mol^2");
2297 }
else if (!
strcmp(Name,
"dBaa")) {
2299 strcpy(units,
"m^3/mol");
2301 }
else if (!
strcmp(Name,
"dCaaa")) {
2302 C_aaa = dCdT_Air(
T);
2303 strcpy(units,
"m^6/mol^2");
2305 }
else if (!
strcmp(Name,
"dBww")) {
2306 B_ww = dBdT_Water(
T);
2307 strcpy(units,
"m^3/mol");
2309 }
else if (!
strcmp(Name,
"dCwww")) {
2310 C_www = dCdT_Water(
T);
2311 strcpy(units,
"m^6/mol^2");
2313 }
else if (!
strcmp(Name,
"Baw")) {
2315 strcpy(units,
"m^3/mol");
2317 }
else if (!
strcmp(Name,
"Caww")) {
2319 strcpy(units,
"m^6/mol^2");
2321 }
else if (!
strcmp(Name,
"Caaw")) {
2323 strcpy(units,
"m^6/mol^2");
2325 }
else if (!
strcmp(Name,
"dBaw")) {
2326 double dB_aw = _dB_aw_dT(
T);
2327 strcpy(units,
"m^3/mol");
2329 }
else if (!
strcmp(Name,
"dCaww")) {
2330 double dC_aww = _dC_aww_dT(
T);
2331 strcpy(units,
"m^6/mol^2");
2333 }
else if (!
strcmp(Name,
"dCaaw")) {
2334 double dC_aaw = _dC_aaw_dT(
T);
2335 strcpy(units,
"m^6/mol^2");
2337 }
else if (!
strcmp(Name,
"beta_H")) {
2339 return HenryConstant(
T);
2340 }
else if (!
strcmp(Name,
"kT")) {
2349 }
else if (!
strcmp(Name,
"p_ws")) {
2352 return IF97::psat97(
T);
2355 }
else if (!
strcmp(Name,
"vbar_ws")) {
2356 strcpy(units,
"m^3/mol");
2366 }
else if (!
strcmp(Name,
"f")) {
2372 if (!
strcmp(Name,
"Bm")) {
2373 strcpy(units,
"m^3/mol");
2374 return B_m(
T, psi_w);
2375 }
else if (!
strcmp(Name,
"Cm")) {
2376 strcpy(units,
"m^6/mol^2");
2377 return C_m(
T, psi_w);
2378 }
else if (!
strcmp(Name,
"hvirial")) {
2380 return 8.3145 *
T * ((B_m(
T, psi_w) -
T * dB_m_dT(
T, psi_w)) / v_bar + (C_m(
T, psi_w) -
T / 2.0 * dC_m_dT(
T, psi_w)) / (v_bar * v_bar));
2394 else if (!
strcmp(Name,
"hbaro_w")) {
2396 }
else if (!
strcmp(Name,
"hbaro_a")) {
2398 }
else if (!
strcmp(Name,
"h_Ice")) {
2401 }
else if (!
strcmp(Name,
"s_Ice")) {
2404 }
else if (!
strcmp(Name,
"psub_Ice")) {
2407 }
else if (!
strcmp(Name,
"g_Ice")) {
2410 }
else if (!
strcmp(Name,
"rho_Ice")) {
2414 std::cout <<
format(
"Sorry I didn't understand your input [%s] to HAProps_Aux\n", Name);
2430 return 2.14627073E+03 - 3.28917768E+01 *
T + 1.89471075E-01 *
T *
T - 4.86290986E-04 *
T *
T *
T + 4.69540143E-07 *
T *
T *
T *
T;
2434 if (!
strcmp(Name,
"s")) {
2435 return s_Ice(
T, p * 1000.0);
2436 }
else if (!
strcmp(Name,
"rho")) {
2438 }
else if (!
strcmp(Name,
"h")) {
2439 return h_Ice(
T, p * 1000.0);
2450# include <catch2/catch_all.hpp>
2452TEST_CASE(
"Check HA Virials from Table A.2.1",
"[RP1485]") {
2454 CHECK(std::abs(HumidAir::B_Air(-60 + 273.15) / (-33.065 / 1e6) - 1) < 1e-3);
2455 CHECK(std::abs(HumidAir::B_Air(0 + 273.15) / (-13.562 / 1e6) - 1) < 1e-3);
2456 CHECK(std::abs(HumidAir::B_Air(200 + 273.15) / (11.905 / 1e6) - 1) < 1e-3);
2457 CHECK(std::abs(HumidAir::B_Air(350 + 273.15) / (18.949 / 1e6) - 1) < 1e-3);
2460 CHECK(std::abs(HumidAir::B_Water(-60 + 273.15) / (-11174 / 1e6) - 1) < 1e-3);
2461 CHECK(std::abs(HumidAir::B_Water(0 + 273.15) / (-2025.6 / 1e6) - 1) < 1e-3);
2462 CHECK(std::abs(HumidAir::B_Water(200 + 273.15) / (-200.52 / 1e6) - 1) < 1e-3);
2463 CHECK(std::abs(HumidAir::B_Water(350 + 273.15) / (-89.888 / 1e6) - 1) < 1e-3);
2466 CHECK(std::abs(HumidAir::_B_aw(-60 + 273.15) / (-68.306 / 1e6) - 1) < 1e-3);
2467 CHECK(std::abs(HumidAir::_B_aw(0 + 273.15) / (-38.074 / 1e6) - 1) < 1e-3);
2468 CHECK(std::abs(HumidAir::_B_aw(200 + 273.15) / (-2.0472 / 1e6) - 1) < 1e-3);
2469 CHECK(std::abs(HumidAir::_B_aw(350 + 273.15) / (7.5200 / 1e6) - 1) < 1e-3);
2473 CHECK(std::abs(HumidAir::C_Air(-60 + 273.15) / (2177.9 / 1e12) - 1) < 1e-3);
2474 CHECK(std::abs(HumidAir::C_Air(0 + 273.15) / (1893.1 / 1e12) - 1) < 1e-3);
2475 CHECK(std::abs(HumidAir::C_Air(200 + 273.15) / (1551.2 / 1e12) - 1) < 1e-3);
2476 CHECK(std::abs(HumidAir::C_Air(350 + 273.15) / (1464.7 / 1e12) - 1) < 1e-3);
2479 CHECK(std::abs(HumidAir::C_Water(-60 + 273.15) / (-1.5162999202e-04) - 1) < 1e-3);
2480 CHECK(std::abs(HumidAir::C_Water(0 + 273.15) / (-10981960 / 1e12) - 1) < 1e-3);
2481 CHECK(std::abs(HumidAir::C_Water(200 + 273.15) / (-0.00000003713759442) - 1) < 1e-3);
2482 CHECK(std::abs(HumidAir::C_Water(350 + 273.15) / (-0.000000001198914198) - 1) < 1e-3);
2485 CHECK(std::abs(HumidAir::_C_aaw(-60 + 273.15) / (1027.3 / 1e12) - 1) < 1e-3);
2486 CHECK(std::abs(HumidAir::_C_aaw(0 + 273.15) / (861.02 / 1e12) - 1) < 1e-3);
2487 CHECK(std::abs(HumidAir::_C_aaw(200 + 273.15) / (627.15 / 1e12) - 1) < 1e-3);
2488 CHECK(std::abs(HumidAir::_C_aaw(350 + 273.15) / (583.79 / 1e12) - 1) < 1e-3);
2491 CHECK(std::abs(HumidAir::_C_aww(-60 + 273.15) / (-1821432 / 1e12) - 1) < 1e-3);
2492 CHECK(std::abs(HumidAir::_C_aww(0 + 273.15) / (-224234 / 1e12) - 1) < 1e-3);
2493 CHECK(std::abs(HumidAir::_C_aww(200 + 273.15) / (-8436.5 / 1e12) - 1) < 1e-3);
2494 CHECK(std::abs(HumidAir::_C_aww(350 + 273.15) / (-2486.9 / 1e12) - 1) < 1e-3);
2497TEST_CASE(
"Enhancement factor from Table A.3",
"[RP1485]") {
2503TEST_CASE(
"Isothermal compressibility from Table A.5",
"[RP1485]") {
2510TEST_CASE(
"Henry constant from Table A.6",
"[RP1485]") {
2511 CHECK(std::abs(HumidAir::HenryConstant(0.010001 + 273.15) / (0.22600e-9) - 1) < 1e-3);
2512 CHECK(std::abs(HumidAir::HenryConstant(300 + 273.15) / (0.58389e-9) - 1) < 1e-3);
2519 std::string in1, in2, in3, out;
2520 double v1, v2, v3, expected;
2521 hel(std::string in1,
double v1, std::string in2,
double v2, std::string in3,
double v3, std::string out,
double expected)
2522 : in1(in1), in2(in2), in3(in3), v1(v1), v2(v2), v3(v3), expected(expected), out(out) {
2526std::vector<hel> table_A11 = {hel(
"T", 473.15,
"W", 0.00,
"P", 101325,
"B", 45.07 + 273.15), hel(
"T", 473.15,
"W", 0.00,
"P", 101325,
"V", 1.341),
2527 hel(
"T", 473.15,
"W", 0.00,
"P", 101325,
"H", 202520), hel(
"T", 473.15,
"W", 0.00,
"P", 101325,
"S", 555.8),
2528 hel(
"T", 473.15,
"W", 0.50,
"P", 101325,
"B", 81.12 + 273.15), hel(
"T", 473.15,
"W", 0.50,
"P", 101325,
"V", 2.416),
2529 hel(
"T", 473.15,
"W", 0.50,
"P", 101325,
"H", 1641400), hel(
"T", 473.15,
"W", 0.50,
"P", 101325,
"S", 4829.5),
2530 hel(
"T", 473.15,
"W", 1.00,
"P", 101325,
"B", 88.15 + 273.15), hel(
"T", 473.15,
"W", 1.00,
"P", 101325,
"V", 3.489),
2531 hel(
"T", 473.15,
"W", 1.00,
"P", 101325,
"H", 3079550), hel(
"T", 473.15,
"W", 1.00,
"P", 101325,
"S", 8889.0)};
2533std::vector<hel> table_A12 = {
2534 hel(
"T", 473.15,
"W", 0.00,
"P", 1e6,
"B", 90.47 + 273.15), hel(
"T", 473.15,
"W", 0.00,
"P", 1e6,
"V", 0.136),
2535 hel(
"T", 473.15,
"W", 0.00,
"P", 1e6,
"H", 201940),
2537 hel(
"T", 473.15,
"W", 0.50,
"P", 1e6,
"B", 148.49 + 273.15), hel(
"T", 473.15,
"W", 0.50,
"P", 1e6,
"V", 0.243),
2538 hel(
"T", 473.15,
"W", 0.50,
"P", 1e6,
"H", 1630140), hel(
"T", 473.15,
"W", 0.50,
"P", 1e6,
"S", 3630.2),
2539 hel(
"T", 473.15,
"W", 1.00,
"P", 1e6,
"B", 159.92 + 273.15), hel(
"T", 473.15,
"W", 1.00,
"P", 1e6,
"V", 0.347),
2540 hel(
"T", 473.15,
"W", 1.00,
"P", 1e6,
"H", 3050210), hel(
"T", 473.15,
"W", 1.00,
"P", 1e6,
"S", 7141.3)};
2542std::vector<hel> table_A15 = {
2543 hel(
"T", 473.15,
"W", 0.10,
"P", 1e7,
"B", 188.92 + 273.15), hel(
"T", 473.15,
"W", 0.10,
"P", 1e7,
"V", 0.016),
2544 hel(
"T", 473.15,
"W", 0.10,
"P", 1e7,
"H", 473920), hel(
"T", 473.15,
"W", 0.10,
"P", 1e7,
"S", -90.1),
2545 hel(
"T", 473.15,
"W", 0.10,
"P", 1e7,
"R", 0.734594),
2548class HAPropsConsistencyFixture
2551 std::vector<hel> inputs;
2552 std::string in1, in2, in3, out;
2553 double v1, v2, v3, expected, actual;
2554 void set_values(hel& h) {
2561 this->expected = h.expected;
2565 actual =
HumidAir::HAPropsSI(out.c_str(), in1.c_str(), v1, in2.c_str(), v2, in3.c_str(), v3);
2569TEST_CASE_METHOD(HAPropsConsistencyFixture,
"ASHRAE RP1485 Tables",
"[RP1485]") {
2570 SECTION(
"Table A.15") {
2572 for (
auto& input : inputs) {
2586 CHECK(std::abs(actual / expected - 1) < 0.01);
2589 SECTION(
"Table A.11") {
2591 for (
auto& input : inputs) {
2605 CHECK(std::abs(actual / expected - 1) < 0.01);
2608 SECTION(
"Table A.12") {
2610 for (
auto& input : inputs) {
2624 CHECK(std::abs(actual / expected - 1) < 0.01);
2628TEST_CASE(
"Assorted tests",
"[HAPropsSI]") {
2642TEST_CASE(
"HAPropsSI rejects out-of-range inverse inputs",
"[HAPropsSI]") {
2643 const double p = 101325, W = 0.11552736;
2644 SECTION(
"T from an impossible enthalpy is rejected (reported case)") {
2647 SECTION(
"the achievable enthalpy still solves correctly and round-trips") {
2650 CHECK(
T == Catch::Approx(58.31 + 273.15).margin(0.5));
2652 CHECK(
HumidAir::HAPropsSI(
"H",
"T",
T,
"P", p,
"W", W) == Catch::Approx(3.5975e5).epsilon(1e-3));
2654 SECTION(
"W from an impossible enthalpy is rejected") {
2667TEST_CASE(
"HAPropsSI is thread-safe under concurrent callers",
"[HAPropsSI][threadsafe]") {
2668 constexpr int n_threads = 16;
2669 constexpr int iters_per_thread = 50;
2670 const double p = 101325;
2676 double W_ref, h_ref;
2678 std::vector<Case> cases;
2679 for (
int i = 0; i < 8; ++i) {
2681 c.T_dry = 280.0 + 5.0 * i;
2682 c.RH = 0.10 + 0.10 * (i % 9);
2688 std::atomic<int> mismatches{0};
2689 std::atomic<int> bad_numbers{0};
2690 std::vector<std::thread> threads;
2691 threads.reserve(n_threads);
2692 for (
int t = 0; t < n_threads; ++t) {
2693 threads.emplace_back([&, t]() {
2694 for (
int it = 0; it < iters_per_thread; ++it) {
2695 const Case& c = cases[(t + it) % cases.size()];
2699 bad_numbers.fetch_add(1, std::memory_order_relaxed);
2702 if (std::abs(W / c.W_ref - 1) > 1e-9 || std::abs(h / c.h_ref - 1) > 1e-9) {
2703 mismatches.fetch_add(1, std::memory_order_relaxed);
2708 for (
auto& th : threads)
2710 CHECK(bad_numbers.load() == 0);
2711 CHECK(mismatches.load() == 0);
2717TEST_CASE(
"HAPropsSI thread_local cost",
"[!benchmark][HAPropsSI][threadsafe]") {
2718 const double p = 101325;
2724 BENCHMARK(
"HAPropsSI H,T=300,R=0.5,P [hot, single-thread]") {
2732 BENCHMARK(
"HAPropsSI H,T=300,R=0.5,P [first call in fresh thread]") {
2733 double result = 0.0;
2741bool is_not_a_pair(
const std::set<std::size_t>& item) {
2742 return item.size() != 2;
2745const int number_of_inputs = 6;
2746std::string inputs[number_of_inputs] = {
"W",
"D",
"B",
"R",
"T",
"V"};
2748class ConsistencyTestData
2752 std::vector<Dictionary> data;
2753 std::list<std::set<std::size_t>> inputs_list;
2754 ConsistencyTestData()
2762 std::vector<std::size_t> indices(number_of_inputs);
2763 for (std::size_t i = 0; i < number_of_inputs; ++i) {
2767 std::set<std::size_t> indices_set(indices.begin(), indices.end());
2768 std::set<std::set<std::size_t>> inputs_powerset =
powerset(indices_set);
2769 inputs_list = std::list<std::set<std::size_t>>(inputs_powerset.begin(), inputs_powerset.end());
2770 inputs_list.remove_if(is_not_a_pair);
2772 const int NT = 10, NW = 5;
2778 const double T_lo = 210.0, T_hi = 350.0;
2779 const double dT = (T_hi - T_lo) / (NT - 1);
2780 for (
int it = 0; it < NT - 1; ++it) {
2781 const double T = T_lo + dT * it;
2783 const double W_lo = 1e-5;
2784 const double dW = (Wsat - W_lo) / (NW - 1);
2785 for (
int iw = 0; iw < NW - 1; ++iw) {
2786 const double W = W_lo + dW * iw;
2789 for (
const auto& input : inputs) {
2793 data.push_back(vals);
2794 std::cout <<
format(
"T %g W %g\n",
T, W);
2801TEST_CASE(
"HAProps tests",
"[HAProps]") {
2802 Eigen::ArrayXd Tdb = Eigen::ArrayXd::LinSpaced(100, -10, 55) + 273.15;
2803 for (
auto Tdb_ : Tdb) {