8#if defined(ENABLE_CATCH)
9# include <catch2/catch_all.hpp>
13#include "boost/math/tools/toms748_solve.hpp"
24 if (!twophase && HEOS.
_T > closest_state.
T) {
62 CoolProp::SaturationSolvers::PTflash_twophase_options o;
63 stability_tester.
get_liq(o.x, o.rhomolar_liq);
64 stability_tester.
get_vap(o.y, o.rhomolar_vap);
69 CoolProp::SaturationSolvers::PTflash_twophase solver(HEOS, o);
72 HEOS.
_Q = (o.z[0] - o.x[0]) / (o.y[0] - o.x[0]);
99 bool saturation_called =
false;
107 throw ValueError(
"twophase not implemented yet");
164 double omega, R, kappa, a, b, A, B, C, Tc, pc, V = 1 / rhomolar;
170 kappa = 0.37464 + 1.54226 * omega - 0.26992 * omega * omega;
171 a = 0.457235 * R * R * Tc * Tc / pc;
172 b = 0.077796 * R * Tc / pc;
173 double den = V * V + 2 * b * V - b * b;
176 A = R * Tc / (V - b) - a * kappa * kappa / (den);
177 B = +2 * a * kappa * (1 + kappa) / (den);
178 C = -a * (1 + 2 * kappa + kappa * kappa) / (den)-p;
182 double sqrt_Tr1 = (-B + sqrt(B * B - 4 * A * C)) / (2 * A);
184 return sqrt_Tr1 * sqrt_Tr1 * Tc;
195 bool saturation_called =
false;
202 if (saturation_called) {
216 if (!std::isfinite(T0)) {
217 throw ValueError(
"Starting value of T0 is not valid in DP_flash");
222 Halley(resid, T0, 1e-10, 100);
268 double Tmin = HEOS.
Tmin() + 0.1;
271 const double eps = 1e-12;
274 format(
"DQ inputs are not defined for density (%g) above critical density (%g) and Q>0", rhomolar, HEOS.
rhomolar_critical()).c_str());
300 if (std::abs(HEOS.
Q() - 1) > 1e-10) {
301 throw ValueError(
format(
"non-unity quality not currently allowed for HQ_flash"));
322 }
else if (std::abs(HEOS.
Q()) < 1e-10) {
333 }
else if (std::abs(HEOS.
Q() - 1) < 1e-10) {
345 throw ValueError(
format(
"non-zero or 1 quality not currently allowed for QS_flash"));
359 auto& superanc = optsuperanc.value();
364 format(
"Temperature to QT_flash [%0.8Lg K] may not be above the numerical critical point of %0.15Lg K", T, Tcrit_num));
366 auto rhoL = superanc.eval_sat(T,
'D', 0);
367 auto rhoV = superanc.eval_sat(T,
'D', 1);
368 auto p = superanc.eval_sat(T,
'P', 1);
369 HEOS.
SatL->update_TDmolarP_unchecked(T, rhoL, p);
370 HEOS.
SatV->update_TDmolarP_unchecked(T, rhoV, p);
372 HEOS.
_rhomolar = 1 / (Q / rhoV + (1 - Q) / rhoL);
385 Tmin_sat = std::max(Tmin_satL, Tmin_satV) - 1e-13;
390 if ((
get_config_bool(CRITICAL_WITHIN_1UK) && std::abs(T - Tmax_sat) < 1e-6) || std::abs(T - Tmax_sat) < 1e-12) {
395 HEOS.
_p = 0.5 * HEOS.
SatV->p() + 0.5 * HEOS.
SatL->p();
397 throw ValueError(
format(
"Temperature to QT_flash [%0.8Lg K] must be in range [%0.8Lg K, %0.8Lg K]", T, Tmin_sat - 0.1, Tmax_sat));
399 double rhoL = _HUGE, rhoV = _HUGE;
404 HEOS.
_p = 0.5 * HEOS.
SatV->p() + 0.5 * HEOS.
SatL->p();
406 }
else if (!(HEOS.
components[0].EOS().pseudo_pure)) {
413 HEOS.
_p = 0.5 * HEOS.
SatV->p() + 0.5 * HEOS.
SatL->p();
417 CoolPropDbl rhoLanc = _HUGE, rhoVanc = _HUGE, rhoLsat = _HUGE, rhoVsat = _HUGE;
420 rhoLanc = HEOS.
components[0].ancillaries.rhoL.evaluate(HEOS.
_T);
421 HEOS.
SatL->update_TP_guessrho(HEOS.
_T, HEOS.
_p, rhoLanc);
425 rhoVanc = HEOS.
components[0].ancillaries.rhoV.evaluate(HEOS.
_T);
426 HEOS.
SatV->update_TP_guessrho(HEOS.
_T, HEOS.
_p, rhoVanc);
429 throw CoolProp::ValueError(
format(
"For pseudo-pure fluid, quality must be equal to 0 or 1. Two-phase quality is not defined"));
463 SaturationSolvers::newton_raphson_saturation NR;
464 SaturationSolvers::newton_raphson_saturation_options IO;
466 IO.bubble_point = (HEOS.
_Q < 0.5);
476 IO.imposed_variable = SaturationSolvers::newton_raphson_saturation_options::T_IMPOSED;
478 if (IO.bubble_point) {
480 NR.call(HEOS, IO.x, IO.y, IO);
483 NR.call(HEOS, IO.y, IO.x, IO);
487 HEOS.
_rhomolar = 1 / (HEOS.
_Q / IO.rhomolar_vap + (1 - HEOS.
_Q) / IO.rhomolar_liq);
497void get_Henrys_coeffs_FP(
const std::string& CAS,
double& A,
double& B,
double& C,
double& Tmin,
double& Tmax) {
499 if (CAS ==
"7440-59-7")
506 }
else if (CAS ==
"7440-01-9")
513 }
else if (CAS ==
"7440-37-1")
520 }
else if (CAS ==
"7439-90-9")
527 }
else if (CAS ==
"7440-63-3")
534 }
else if (CAS ==
"1333-74-0")
541 }
else if (CAS ==
"7727-37-9")
548 }
else if (CAS ==
"7782-44-7")
555 }
else if (CAS ==
"630-08-0")
562 }
else if (CAS ==
"124-38-9")
569 }
else if (CAS ==
"7783-06-4")
576 }
else if (CAS ==
"74-82-8")
583 }
else if (CAS ==
"74-84-0")
590 }
else if (CAS ==
"2551-62-4")
598 throw ValueError(
"Bad component in Henry's law constants");
607 auto& superanc = optsuperanc.value();
609 if (HEOS.
_p > pmax_num) {
611 format(
"Pressure to PQ_flash [%0.8Lg Pa] may not be above the numerical critical point of %0.15Lg Pa", HEOS.
_p, pmax_num));
613 auto T = superanc.get_T_from_p(HEOS.
_p);
614 auto rhoL = superanc.eval_sat(T,
'D', 0);
615 auto rhoV = superanc.eval_sat(T,
'D', 1);
617 HEOS.
SatL->update_TDmolarP_unchecked(T, rhoL, p);
618 HEOS.
SatV->update_TDmolarP_unchecked(T, rhoV, p);
636 HEOS.
SatL->update_TP_guessrho(HEOS.
_TLanc, HEOS.
_p, rhoL);
637 HEOS.
SatV->update_TP_guessrho(HEOS.
_TVanc, HEOS.
_p, rhoV);
641 HEOS.
_p = HEOS.
_Q * HEOS.
SatV->p() + (1 - HEOS.
_Q) * HEOS.
SatL->p();
642 HEOS.
_T = HEOS.
_Q * HEOS.
SatV->T() + (1 - HEOS.
_Q) * HEOS.
SatL->T();
651 pmin_sat = std::max(pmin_satL, pmin_satV);
666 throw ValueError(
format(
"Pressure to PQ_flash [%6g Pa] must be in range [%8Lg Pa, %8Lg Pa]", HEOS.
_p, pmin_sat, pmax_sat));
680 double increment = 0.4;
683 for (
double omega = 1.0; omega > 0; omega -= increment) {
685 options.
omega = omega;
693 if (omega < 1.1 * increment) {
706 HEOS.
_p = HEOS.
_Q * HEOS.
SatV->p() + (1 - HEOS.
_Q) * HEOS.
SatL->p();
726 std::vector<CoolPropDbl> K = HEOS.
K;
728 if (
get_config_bool(HENRYS_LAW_TO_GENERATE_VLE_GUESSES) && std::abs(HEOS.
_Q - 1) < 1e-10) {
729 const std::vector<CoolPropFluid>& components = HEOS.
get_components();
730 std::size_t iWater = 0;
731 double p1star =
PropsSI(
"P",
"T", Tguess,
"Q", 1,
"Water");
733 std::vector<CoolPropDbl> x(y.size());
734 for (std::size_t i = 0; i < components.size(); ++i) {
735 if (components[i].CAS ==
"7732-18-5") {
739 double A, B, C, Tmin, Tmax;
741 double T_R = Tguess / 647.096, tau = 1 - T_R;
742 double k_H = p1star * exp(A / T_R + B * pow(tau, 0.355) / T_R + C * pow(T_R, -0.41) * exp(tau));
743 x[i] = y[i] * HEOS.
_p / k_H;
750 for (std::size_t i = 0; i < y.size(); ++i) {
756 K[iWater] = y[iWater] / x[iWater];
766 SaturationSolvers::newton_raphson_saturation NR;
767 SaturationSolvers::newton_raphson_saturation_options IO;
769 IO.bubble_point = (HEOS.
_Q < 0.5);
777 IO.imposed_variable = SaturationSolvers::newton_raphson_saturation_options::P_IMPOSED;
779 if (IO.bubble_point) {
781 NR.call(HEOS, IO.x, IO.y, IO);
784 NR.call(HEOS, IO.y, IO.x, IO);
797 SaturationSolvers::newton_raphson_saturation NR;
798 SaturationSolvers::newton_raphson_saturation_options IO;
801 IO.x = std::vector<CoolPropDbl>(guess.
x.begin(), guess.
x.end());
802 IO.y = std::vector<CoolPropDbl>(guess.
y.begin(), guess.
y.end());
805 IO.bubble_point =
false;
806 IO.imposed_variable = SaturationSolvers::newton_raphson_saturation_options::P_IMPOSED;
808 if (std::abs(HEOS.
Q()) < 1e-10) {
809 IO.bubble_point =
true;
810 NR.call(HEOS, IO.x, IO.y, IO);
811 }
else if (std::abs(HEOS.
Q() - 1) < 1e-10) {
812 IO.bubble_point =
false;
813 NR.call(HEOS, IO.y, IO.x, IO);
820 HEOS.
_rhomolar = 1 / (HEOS.
_Q / IO.rhomolar_vap + (1 - HEOS.
_Q) / IO.rhomolar_liq);
824 SaturationSolvers::newton_raphson_saturation NR;
825 SaturationSolvers::newton_raphson_saturation_options IO;
828 IO.x = std::vector<CoolPropDbl>(guess.
x.begin(), guess.
x.end());
829 IO.y = std::vector<CoolPropDbl>(guess.
y.begin(), guess.
y.end());
832 IO.bubble_point =
false;
833 IO.imposed_variable = SaturationSolvers::newton_raphson_saturation_options::T_IMPOSED;
836 std::cout <<
format(
" QT w/ guess p %g T %g dl %g dv %g x %s y %s\n", IO.p, IO.T, IO.rhomolar_liq, IO.rhomolar_vap,
840 if (std::abs(HEOS.
Q()) < 1e-10) {
841 IO.bubble_point =
true;
842 NR.call(HEOS, IO.x, IO.y, IO);
843 }
else if (std::abs(HEOS.
Q() - 1) < 1e-10) {
844 IO.bubble_point =
false;
845 NR.call(HEOS, IO.y, IO.x, IO);
853 HEOS.
_rhomolar = 1 / (HEOS.
_Q / IO.rhomolar_vap + (1 - HEOS.
_Q) / IO.rhomolar_liq);
884 std::vector<std::pair<std::size_t, std::size_t>> intersections =
895 quality_options quality;
897 quality = SATURATED_LIQUID;
899 quality = SATURATED_VAPOR;
900 }
else if (HEOS.
_Q > 0 && HEOS.
_Q < 1) {
903 throw ValueError(
"Quality is not within 0 and 1");
906 if (quality == SATURATED_LIQUID || quality == SATURATED_VAPOR) {
911 std::vector<std::size_t> solutions;
912 for (std::vector<std::pair<std::size_t, std::size_t>>::const_iterator it = intersections.begin(); it != intersections.end(); ++it) {
914 solutions.push_back(it->first);
918 if (solutions.size() == 1) {
920 std::size_t& imax = solutions[0];
923 if (imax + 2 >= env.T.size()) {
925 }
else if (imax == 0) {
933 SaturationSolvers::newton_raphson_saturation NR;
934 SaturationSolvers::newton_raphson_saturation_options IO;
938 IO.imposed_variable = SaturationSolvers::newton_raphson_saturation_options::P_IMPOSED;
940 IO.rhomolar_vap =
CubicInterp(env.p, env.rhomolar_vap, imax - 1, imax, imax + 1, imax + 2,
static_cast<CoolPropDbl>(IO.p));
941 IO.T =
CubicInterp(env.rhomolar_vap, env.T, imax - 1, imax, imax + 1, imax + 2, IO.rhomolar_vap);
942 }
else if (other ==
iT) {
944 IO.imposed_variable = SaturationSolvers::newton_raphson_saturation_options::T_IMPOSED;
946 IO.rhomolar_vap =
CubicInterp(env.T, env.rhomolar_vap, imax - 1, imax, imax + 1, imax + 2,
static_cast<CoolPropDbl>(IO.T));
947 IO.p =
CubicInterp(env.rhomolar_vap, env.p, imax - 1, imax, imax + 1, imax + 2, IO.rhomolar_vap);
951 IO.rhomolar_liq =
CubicInterp(env.rhomolar_vap, env.rhomolar_liq, imax - 1, imax, imax + 1, imax + 2, IO.rhomolar_vap);
953 if (quality == SATURATED_VAPOR) {
954 IO.bubble_point =
false;
956 IO.x.resize(IO.y.size());
957 for (std::size_t i = 0; i < IO.x.size() - 1; ++i)
959 IO.x[i] =
CubicInterp(env.rhomolar_vap, env.x[i], imax - 1, imax, imax + 1, imax + 2, IO.rhomolar_vap);
961 IO.x[IO.x.size() - 1] = 1 - std::accumulate(IO.x.begin(), IO.x.end() - 1, 0.0);
962 NR.call(HEOS, IO.y, IO.x, IO);
964 IO.bubble_point =
true;
966 IO.y.resize(IO.x.size());
968 std::swap(IO.rhomolar_liq, IO.rhomolar_vap);
969 for (std::size_t i = 0; i < IO.y.size() - 1; ++i)
973 IO.y[i] =
CubicInterp(env.rhomolar_vap, env.x[i], imax - 1, imax, imax + 1, imax + 2, IO.rhomolar_liq);
975 IO.y[IO.y.size() - 1] = 1 - std::accumulate(IO.y.begin(), IO.y.end() - 1, 0.0);
976 NR.call(HEOS, IO.x, IO.y, IO);
978 }
else if (solutions.size() == 0) {
979 throw ValueError(
"No solution was found in PQ_flash");
981 throw ValueError(
"More than 1 solution was found in PQ_flash");
989 std::vector<std::size_t> liquid_solutions, vapor_solutions;
990 for (std::vector<std::pair<std::size_t, std::size_t>>::const_iterator it = intersections.begin(); it != intersections.end(); ++it) {
991 if (std::abs(env.Q[it->first] - 0) < 10 *
DBL_EPSILON && std::abs(env.Q[it->second] - 0) < 10 *
DBL_EPSILON) {
992 liquid_solutions.push_back(it->first);
994 if (std::abs(env.Q[it->first] - 1) < 10 *
DBL_EPSILON && std::abs(env.Q[it->second] - 1) < 10 *
DBL_EPSILON) {
995 vapor_solutions.push_back(it->first);
999 if (liquid_solutions.size() != 1 || vapor_solutions.size() != 1) {
1000 throw ValueError(
format(
"Number liquid solutions [%d] or vapor solutions [%d] != 1", liquid_solutions.size(), vapor_solutions.size()));
1002 std::size_t iliq = liquid_solutions[0], ivap = vapor_solutions[0];
1004 SaturationSolvers::newton_raphson_twophase NR;
1005 SaturationSolvers::newton_raphson_twophase_options IO;
1008 CoolPropDbl rhomolar_vap_sat_vap, T_sat_vap, rhomolar_liq_sat_vap, rhomolar_liq_sat_liq, T_sat_liq, rhomolar_vap_sat_liq, p_sat_liq,
1015 IO.imposed_variable = SaturationSolvers::newton_raphson_twophase_options::P_IMPOSED;
1018 rhomolar_vap_sat_vap =
CubicInterp(env.p, env.rhomolar_vap, ivap - 1, ivap, ivap + 1, ivap + 2,
static_cast<CoolPropDbl>(IO.p));
1019 T_sat_vap =
CubicInterp(env.rhomolar_vap, env.T, ivap - 1, ivap, ivap + 1, ivap + 2, rhomolar_vap_sat_vap);
1020 rhomolar_liq_sat_vap =
CubicInterp(env.rhomolar_vap, env.rhomolar_liq, ivap - 1, ivap, ivap + 1, ivap + 2, rhomolar_vap_sat_vap);
1023 rhomolar_liq_sat_liq =
CubicInterp(env.p, env.rhomolar_vap, iliq - 1, iliq, iliq + 1, iliq + 2,
static_cast<CoolPropDbl>(IO.p));
1024 T_sat_liq =
CubicInterp(env.rhomolar_vap, env.T, iliq - 1, iliq, iliq + 1, iliq + 2, rhomolar_liq_sat_liq);
1025 rhomolar_vap_sat_liq =
CubicInterp(env.rhomolar_vap, env.rhomolar_liq, iliq - 1, iliq, iliq + 1, iliq + 2, rhomolar_liq_sat_liq);
1026 }
else if (other ==
iT) {
1030 IO.imposed_variable = SaturationSolvers::newton_raphson_twophase_options::T_IMPOSED;
1033 rhomolar_vap_sat_vap =
CubicInterp(env.T, env.rhomolar_vap, ivap - 1, ivap, ivap + 1, ivap + 2,
static_cast<CoolPropDbl>(IO.T));
1034 p_sat_vap =
CubicInterp(env.rhomolar_vap, env.p, ivap - 1, ivap, ivap + 1, ivap + 2, rhomolar_vap_sat_vap);
1035 rhomolar_liq_sat_vap =
CubicInterp(env.rhomolar_vap, env.rhomolar_liq, ivap - 1, ivap, ivap + 1, ivap + 2, rhomolar_vap_sat_vap);
1038 rhomolar_liq_sat_liq =
CubicInterp(env.T, env.rhomolar_vap, iliq - 1, iliq, iliq + 1, iliq + 2,
static_cast<CoolPropDbl>(IO.T));
1039 p_sat_liq =
CubicInterp(env.rhomolar_vap, env.p, iliq - 1, iliq, iliq + 1, iliq + 2, rhomolar_liq_sat_liq);
1040 rhomolar_vap_sat_liq =
CubicInterp(env.rhomolar_vap, env.rhomolar_liq, iliq - 1, iliq, iliq + 1, iliq + 2, rhomolar_liq_sat_liq);
1046 IO.rhomolar_vap = IO.beta * rhomolar_vap_sat_vap + (1 - IO.beta) * rhomolar_vap_sat_liq;
1047 IO.rhomolar_liq = IO.beta * rhomolar_liq_sat_vap + (1 - IO.beta) * rhomolar_liq_sat_liq;
1048 IO.T = IO.beta * T_sat_vap + (1 - IO.beta) * T_sat_liq;
1049 IO.p = IO.beta * p_sat_vap + (1 - IO.beta) * p_sat_liq;
1052 IO.x.resize(IO.z.size());
1053 IO.y.resize(IO.z.size());
1055 for (std::size_t i = 0; i < IO.x.size() - 1; ++i)
1057 CoolPropDbl x_sat_vap =
CubicInterp(env.rhomolar_vap, env.x[i], ivap - 1, ivap, ivap + 1, ivap + 2, rhomolar_vap_sat_vap);
1058 CoolPropDbl y_sat_vap =
CubicInterp(env.rhomolar_vap, env.y[i], ivap - 1, ivap, ivap + 1, ivap + 2, rhomolar_vap_sat_vap);
1060 CoolPropDbl x_sat_liq =
CubicInterp(env.rhomolar_vap, env.y[i], iliq - 1, iliq, iliq + 1, iliq + 2, rhomolar_liq_sat_liq);
1061 CoolPropDbl y_sat_liq =
CubicInterp(env.rhomolar_vap, env.x[i], iliq - 1, iliq, iliq + 1, iliq + 2, rhomolar_liq_sat_liq);
1063 IO.x[i] = IO.beta * x_sat_vap + (1 - IO.beta) * x_sat_liq;
1064 IO.y[i] = IO.beta * y_sat_vap + (1 - IO.beta) * y_sat_liq;
1066 IO.x[IO.x.size() - 1] = 1 - std::accumulate(IO.x.begin(), IO.x.end() - 1, 0.0);
1067 IO.y[IO.y.size() - 1] = 1 - std::accumulate(IO.y.begin(), IO.y.end() - 1, 0.0);
1082 : HEOS(HEOS), rhomolar_spec(rhomolar_spec), other(other), value(value) {
1085 double call(
double T) {
1089 Qd = (1 / rhomolar_spec - 1 / SatL.
rhomolar()) / (1 / SatV.rhomolar() - 1 / SatL.
rhomolar());
1095 } resid(HEOS, rhomolar_spec, other, value);
1103 Tmin_sat = std::max(Tmin_satL, Tmin_satV) - 1e-13;
1121 : HEOS(HEOS), rhomolar(rhomolar), value(value), other(other), Tmin(Tmin), Tmax(Tmax) {
1125 double call(
double T) {
1130 return (eos - value) / value;
1136 double deriv(
double T) {
1142 double second_deriv(
double T) {
1148 bool input_not_in_range(
double T) {
1149 return (T < Tmin || T > Tmax);
1156 shared_ptr<HelmholtzEOSMixtureBackend> Sat;
1190 y_solid = (yV - yL) / (1 / rhoVtriple - 1 / rhoLtriple) * (1 / HEOS.
_rhomolar - 1 / rhoLtriple) + yL;
1192 if (value < y_solid) {
1201 for (
int i_try = 0; i_try < 7; i_try++) {
1216 optionsD.
omega /= 2;
1225 if (value > Sat->keyed_output(other)) {
1226 solver_resid resid(&HEOS, HEOS.
_rhomolar, value, other, Sat->keyed_output(
iT), HEOS.
Tmax() * 1.5);
1228 HEOS.
_T =
Halley(resid, 0.5 * (Sat->keyed_output(
iT) + HEOS.
Tmax() * 1.5), 1e-10, 100);
1254 HEOS.
_Q = (1 / HEOS.
_rhomolar - 1 / HEOS.
SatL->rhomolar()) / (1 / HEOS.
SatV->rhomolar() - 1 / HEOS.
SatL->rhomolar());
1255 HEOS.
_T = HEOS.
SatL->T();
1291 solver_resid resid(&HEOS, HEOS.
_rhomolar, value, other, TVtriple, HEOS.
Tmax() * 1.5);
1332 solver_resid resid(&HEOS, HEOS.
_rhomolar, value, other, TLtriple, HEOS.
Tmax() * 1.5);
1354 double A[2][2], B[2][2];
1370 throw ValueError(
"other is invalid in HSU_P_flash_singlephase_Newton");
1375 bool failed =
false;
1376 CoolPropDbl omega = 1.0, f2, df2_dtau, df2_ddelta;
1378 while (worst_error > 1e-6 && failed ==
false) {
1394 CoolPropDbl f1 = delta / tau * (1 + delta * dar_ddelta) - p / (rhoc * R * Tc);
1395 CoolPropDbl df1_dtau = (1 + delta * dar_ddelta) * (-delta / tau / tau) + delta / tau * (delta * d2ar_ddelta_dtau);
1396 CoolPropDbl df1_ddelta = (1.0 / tau) * (1 + 2.0 * delta * dar_ddelta + delta * delta * d2ar_ddelta2);
1399 f2 = (1 + delta * dar_ddelta) + tau * (da0_dtau + dar_dtau) - tau * y / (R * Tc);
1400 df2_dtau = delta * d2ar_ddelta_dtau + da0_dtau + dar_dtau + tau * (d2a0_dtau2 + d2ar_dtau2) - y / (R * Tc);
1401 df2_ddelta = (dar_ddelta + delta * d2ar_ddelta2) + tau * (d2a0_ddelta_dtau + d2ar_ddelta_dtau);
1405 f2 = tau * (da0_dtau + dar_dtau) - ar - a0 - y / R;
1406 df2_dtau = tau * (d2a0_dtau2 + d2ar_dtau2) + (da0_dtau + dar_dtau) - dar_dtau - da0_dtau;
1407 df2_ddelta = tau * (d2a0_ddelta_dtau + d2ar_ddelta_dtau) - dar_ddelta - da0_ddelta;
1411 throw ValueError(
"other variable in HSU_P_flash_singlephase_Newton is invalid");
1416 A[0][1] = df1_ddelta;
1418 A[1][1] = df2_ddelta;
1423 tau -= omega * (B[0][0] * f1 + B[0][1] * f2);
1424 delta -= omega * (B[1][0] * f1 + B[1][1] * f2);
1426 if (std::abs(f1) > std::abs(f2))
1427 worst_error = std::abs(f1);
1429 worst_error = std::abs(f2);
1432 throw SolutionError(
format(
"Invalid values for inputs p=%g y=%g for fluid %s in HSU_P_flash_singlephase", p, y, _HEOS.
name().c_str()));
1437 throw SolutionError(
format(
"HSU_P_flash_singlephase did not converge with inputs p=%g h=%g for fluid %s", p, y, _HEOS.
name().c_str()));
1446 throw ValueError(
"value for p in HSU_P_flash_singlephase_Brent is invalid");
1449 throw ValueError(
"value for other in HSU_P_flash_singlephase_Brent is invalid");
1458 CoolPropDbl eos0, eos1, rhomolar, rhomolar0, rhomolar1;
1485 double call(
double T) {
1487 if (iter < 2 || std::abs(rhomolar1 / rhomolar0 - 1) > 0.05) {
1501 if (verbosity > 0 && iter == 0) {
1502 std::cout <<
format(
"T: %0.15g rho: %0.15g eos: %0.15g", T, rhomolar, eos);
1511 rhomolar0 = rhomolar;
1512 }
else if (iter == 1) {
1514 rhomolar1 = rhomolar;
1518 rhomolar0 = rhomolar1;
1519 rhomolar1 = rhomolar;
1525 double deriv(
double T) {
1528 double second_deriv(
double T) {
1531 bool input_not_in_range(
double x) {
1532 return (x < Tmin || x > Tmax);
1535 solver_resid resid(&HEOS, HEOS.
_p, value, other, Tmin, Tmax);
1544 std::vector<std::vector<double>> J = {{-1.0, -1.0}, {-1.0, -1.0}};
1547 : HEOS(HEOS), p(p), value(value), other(other), iter(0) {}
1548 std::vector<double> call(
const std::vector<double>& x)
override {
1549 double T = x[0], rhomolar = x[1];
1556 return {(HEOS->
p() - p) / p, HEOS->
keyed_output(other) - value};
1558 std::vector<std::vector<double>> Jacobian(
const std::vector<double>& )
override {
1563 resid_2D solver_resid2d(&HEOS, HEOS.
_p, value, other, Tmin, Tmax);
1566 double resid_Tmin = resid.call(Tmin);
1567 double rhomolar_Tmin = HEOS.
rhomolar();
1569 double resid_Tmax = resid.call(Tmax);
1570 double rhomolar_Tmax = HEOS.
rhomolar();
1573 bool use_min = std::abs(resid_Tmin) < std::abs(resid_Tmax);
1574 double Tstart = use_min ? Tmin : Tmax;
1575 double rhomolarstart = use_min ? rhomolar_Tmin : rhomolar_Tmax;
1579 resid.verbosity = 1;
1581 if (resid_Tmin * resid_Tmax < 0) {
1589 boost::math::uintmax_t max_iter = 100;
1591 auto f = [&resid](
const double T) {
return resid.call(T); };
1595 auto [l, r] = toms748_solve(f, Tmin, Tmax, resid_Tmin, resid_Tmax, boost::math::tools::eps_tolerance<double>(44), max_iter);
1605 Halley(resid, Tstart, 1e-12, 100);
1607 throw ValueError(
"Halley's method was unable to find a solution in HSU_P_flash_singlephase_Brent");
1616 if (0.95 * p_critical_ > HEOS.
_p || HEOS.
_p > p_critical_) {
1620 std::cout << resid.errstring << std::endl;
1622 std::vector<double> x0 = {Tstart, rhomolarstart};
1625 throw ValueError(
"2D Newton method was unable to find a solution in HSU_P_flash_singlephase_Brent");
1632 std::cout << resid.errstring << std::endl;
1642 if (eos1 > eos0 && value > eos1) {
1644 format(
"HSU_P_flash_singlephase_Brent could not find a solution because %s [%Lg %s] is above the maximum value of %0.12Lg %s",
1645 name.c_str(), value, units.c_str(), eos1, units.c_str()));
1647 if (eos1 > eos0 && value < eos0) {
1649 format(
"HSU_P_flash_singlephase_Brent could not find a solution because %s [%Lg %s] is below the minimum value of %0.12Lg %s",
1650 name.c_str(), value, units.c_str(), eos0, units.c_str()));
1659 bool saturation_called =
false;
1687 Tmax = 1.5 * HEOS.
Tmax();
1695 auto& superanc = optsuperanc.value();
1697 if (HEOS.
_p > pmax_num) {
1699 format(
"Pressure to PQ_flash [%0.8Lg Pa] may not be above the numerical critical point of %0.15Lg Pa", HEOS.
_p,
1702 Tmin = superanc.get_T_from_p(HEOS.
_p);
1706 if (saturation_called) {
1707 Tmin = HEOS.
SatV->T();
1720 Tmin = HEOS.
Tmin() - 1e-3;
1726 auto& superanc = optsuperanc.value();
1728 if (HEOS.
_p > pmax_num) {
1730 "Pressure to PQ_flash [%0.8Lg Pa] may not be above the numerical critical point of %0.15Lg Pa", HEOS.
_p, pmax_num));
1732 Tmax = superanc.get_T_from_p(HEOS.
_p);
1737 if (saturation_called) {
1738 Tmax = HEOS.
SatL->T();
1748 Tmax = 1.5 * HEOS.
Tmax();
1753 Tmin = HEOS.
Tmin() - 1e-3;
1763 }
catch (std::exception& e) {
1764 throw ValueError(
format(
"unable to solve 1phase PY flash with Tmin=%Lg, Tmax=%Lg due to error: %s", Tmin, Tmax, e.what()));
1774 std::size_t iclosest;
1783 throw ValueError(
"two-phase solution for Y");
1787 throw ValueError(
"phase envelope must be built to carry out HSU_P_flash for mixture");
1801 : HEOS(HEOS), T(T), value(value), other(other) {}
1802 double call(
double rhomolar) {
1807 double deriv(
double rhomolar) {
1810 double second_deriv(
double rhomolar) {
1814 solver_resid resid(&HEOS, T, value, other);
1819 if (HEOS.
_T > T_critical_) {
1848 Brent(resid, rhoc, rhomin, LDBL_EPSILON, 1e-9, 100);
1849 }
else if (y < yc) {
1867 if (step_count > 30) {
1868 throw ValueError(
format(
"Even by increasing rhoc, not able to bound input; input %Lg is not in range %Lg,%Lg", y, yc, ymin));
1872 Brent(resid, rhomin, rhoc, LDBL_EPSILON, 1e-9, 100);
1874 throw ValueError(
format(
"input %Lg is not in range %Lg,%Lg,%Lg", y, yc, ymin));
1912 CoolPropDbl rhomolar_guess = (rhomelt - rhoL) / (ymelt - yL) * (y - yL) + rhoL;
1915 Halley(resid, rhomolar_guess, 1e-8, 100);
1917 Secant(resid, rhomolar_guess, 0.0001 * rhomolar_guess, 1e-12, 100);
1926 Halley(resid, 0.5 * (rhomin + rhoV), 1e-8, 100);
1929 Brent(resid, rhomin, rhoV, LDBL_EPSILON, 1e-12, 100);
1935 throw ValueError(
format(
"phase to solver_for_rho_given_T_oneof_HSU is invalid"));
1946 if (HEOS.
_T < T_critical_)
1976 Q = (1 / HEOS.
rhomolar() - 1 / HEOS1.
SatL->rhomolar()) / (1 / HEOS1.
SatV->rhomolar() - 1 / HEOS1.
SatL->rhomolar());
1979 Q = (HEOS.
smolar() - HEOS1.
SatL->smolar()) / (HEOS1.
SatV->smolar() - HEOS1.
SatL->smolar());
1982 Q = (HEOS.
hmolar() - HEOS1.
SatL->hmolar()) / (HEOS1.
SatV->hmolar() - HEOS1.
SatL->hmolar());
1985 Q = (HEOS.
umolar() - HEOS1.
SatL->umolar()) / (HEOS1.
SatV->umolar() - HEOS1.
SatL->umolar());
1997 HEOS.
_p = HEOS.
_Q * HEOS1.
SatV->p() + (1 - HEOS.
_Q) * HEOS1.
SatL->p();
2003 throw ValueError(
format(
"Temperature specified is not the imposed phase region."));
2004 }
else if (HEOS.
_T > T_critical_ && HEOS.
_T > HEOS.
components[0].EOS().Ttriple) {
2068 : HEOS(HEOS), hmolar(hmolar_spec), smolar(smolar_spec), Qs(_HUGE) {};
2069 double call(
double T) {
2073 Qs = (smolar - SatL.
smolar()) / (SatV.smolar() - SatL.
smolar());
2079 } resid(HEOS, hmolar_spec, smolar_spec);
2087 Tmin_sat = std::max(Tmin_satL, Tmin_satV) - 1e-13;
2096 double resid = 9e30, resid_old = 9e30;
2102 r(0) = HEOS.
hmolar() - hmolar_spec;
2103 r(1) = HEOS.
smolar() - smolar_spec;
2109 Eigen::Vector2d v = J.colPivHouseholderQr().solve(-r);
2110 bool good_solution =
false;
2111 double tau0 = HEOS.
tau(), delta0 = HEOS.
delta();
2114 for (
double frac = 1.0; frac > 0.001; frac /= 2) {
2117 double tau_new = tau0 + options.
omega * frac * v(0);
2118 double delta_new = delta0 + options.
omega * frac * v(1);
2119 double T_new = reducing.
T / tau_new;
2120 double rhomolar_new = delta_new * reducing.
rhomolar;
2124 if (resid > resid_old) {
2125 throw ValueError(
format(
"residual not decreasing; frac: %g, resid: %g, resid_old: %g", frac, resid, resid_old));
2127 good_solution =
true;
2134 if (!good_solution) {
2139 throw ValueError(
format(
"HS_flash_singlephase took too many iterations; residual is %g; prior was %g", resid, resid_old));
2141 }
while (std::abs(resid) > 1e-9);
2145 double logp = ((double)rand() / (double)RAND_MAX) * (log(HEOS.
pmax()) - log(HEOS.
p_triple())) + log(HEOS.
p_triple());
2146 T = ((double)rand() / (double)RAND_MAX) * (HEOS.
Tmax() - HEOS.
Ttriple()) + HEOS.
Ttriple();
2159 : HEOS(HEOS), hmolar(hmolar_spec), smolar(smolar_spec) {};
2160 double call(
double T) {
2162 double r = HEOS.
hmolar() - hmolar;
2165 } resid(HEOS, hmolar, smolar);
2168 bool good_Tmin =
false;
2173 rmin = resid.call(Tmin);
2178 if (Tmin > HEOS.
Tmax()) {
2181 }
while (!good_Tmin);
2184 bool good_Tmax =
false;
2185 double Tmax = HEOS.
Tmax() * 1.01;
2189 rmax = resid.call(Tmax);
2197 }
while (!good_Tmax);
2198 if (rmin * rmax > 0 && std::abs(rmax) < std::abs(rmin)) {
2204#if defined(ENABLE_CATCH)
2206TEST_CASE(
"PD with T very large should yield error",
"[PDflash]") {
2208 double Tc = HEOS->T_critical();
2210 CHECK_THROWS(HEOS->update(
DmassP_INPUTS, 2, 5 * HEOS->p()));
2213TEST_CASE(
"Stability testing",
"[stability]") {
2214 shared_ptr<HelmholtzEOSMixtureBackend> HEOS(
new HelmholtzEOSMixtureBackend(
strsplit(
"n-Propane&n-Butane&n-Pentane&n-Hexane",
'&')));
2215 std::vector<double> z(4);
2220 HEOS->set_mole_fractions(z);
2223 double TL = HEOS->T();
2226 double TV = HEOS->T();
2228 SECTION(
"Liquid (feed is stable)") {
2229 StabilityRoutines::StabilityEvaluationClass stability_tester(*HEOS);
2230 for (
double T = TL - 1; T >= 100; T -= 1) {
2231 stability_tester.set_TP(T, 101325);
2233 CHECK_NOTHROW(stability_tester.is_stable());
2236 SECTION(
"Vapor (feed is stable)") {
2237 StabilityRoutines::StabilityEvaluationClass stability_tester(*HEOS);
2238 for (
double T = TV + 1; T <= 500; T += 1) {
2239 stability_tester.set_TP(T, 101325);
2241 CHECK_NOTHROW(stability_tester.is_stable());
2244 SECTION(
"Two-phase (feed is unstable)") {
2245 StabilityRoutines::StabilityEvaluationClass stability_tester(*HEOS);
2246 stability_tester.set_TP((TV + TL) / 2.0, 101325);
2247 CHECK(stability_tester.is_stable() ==
false);
2251TEST_CASE(
"Test critical points for methane + H2S",
"[critical_points]") {
2252 shared_ptr<HelmholtzEOSMixtureBackend> HEOS(
new HelmholtzEOSMixtureBackend(
strsplit(
"Methane&H2S",
'&')));
2254 double zz[] = {0.998, 0.97, 0.9475, 0.94, 0.93, 0.86, 0.85, 0.84, 0.75, 0.53, 0.52, 0.51, 0.49, 0.36, 0.24, 0.229, 0.09};
2255 int Npts[] = {2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 1, 1, 1, 1};
2256 int imax =
sizeof(zz) /
sizeof(
double);
2258 for (
int i = 0; i < imax; ++i) {
2260 std::vector<double> z(2);
2263 HEOS->set_mole_fractions(z);
2265 std::vector<CriticalState> pts = HEOS->all_critical_points();
2266 CHECK(pts.size() == Npts[i]);
2270TEST_CASE(
"Test critical points for nitrogen + ethane with HEOS",
"[critical_points]") {
2271 shared_ptr<HelmholtzEOSMixtureBackend> HEOS(
new HelmholtzEOSMixtureBackend(
strsplit(
"Nitrogen&Ethane",
'&')));
2272 std::vector<double> zz =
linspace(0.001, 0.999, 21);
2273 int failure_count = 0;
2274 for (
int i = 0; i < static_cast<std::size_t>(zz.size()); ++i) {
2276 std::vector<double> z(2);
2279 HEOS->set_mole_fractions(z);
2281 std::vector<CriticalState> pts;
2283 pts = HEOS->all_critical_points();
2284 }
catch (std::exception& e) {
2290 CHECK(failure_count < 10);
2293TEST_CASE(
"Test critical points for nitrogen + ethane with PR",
"[critical_points]") {
2294 shared_ptr<PengRobinsonBackend> HEOS(
new PengRobinsonBackend(
strsplit(
"Nitrogen&Ethane",
'&')));
2295 HEOS->set_binary_interaction_double(0, 1,
"kij", 0.0407);
2296 std::vector<double> zz =
linspace(0.001, 0.999, 21);
2297 for (
int i = 0; i < static_cast<std::size_t>(zz.size()); ++i) {
2299 std::vector<double> z(2);
2302 HEOS->set_mole_fractions(z);
2304 std::vector<CriticalState> pts;
2305 CHECK_NOTHROW(pts = HEOS->all_critical_points());