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;
299 if (std::abs(HEOS.
Q() - 1) > 1e-10) {
300 throw ValueError(
format(
"non-unity quality not currently allowed for HQ_flash"));
321 }
else if (std::abs(HEOS.
Q()) < 1e-10) {
332 }
else if (std::abs(HEOS.
Q() - 1) < 1e-10) {
344 throw ValueError(
format(
"non-zero or 1 quality not currently allowed for QS_flash"));
358 auto& superanc = optsuperanc.value();
362 throw ValueError(
format(
"Temperature to QT_flash [%0.8Lg K] may not be above the numerical critical point of %0.15Lg K", T, Tcrit_num));
364 auto rhoL = superanc.eval_sat(T,
'D', 0);
365 auto rhoV = superanc.eval_sat(T,
'D', 1);
366 auto p = superanc.eval_sat(T,
'P', 1);
367 HEOS.
SatL->update_TDmolarP_unchecked(T, rhoL, p);
368 HEOS.
SatV->update_TDmolarP_unchecked(T, rhoV, p);
370 HEOS.
_rhomolar = 1 / (Q / rhoV + (1 - Q) / rhoL);
384 Tmin_sat = std::max(Tmin_satL, Tmin_satV) - 1e-13;
389 if ((
get_config_bool(CRITICAL_WITHIN_1UK) && std::abs(T - Tmax_sat) < 1e-6) || std::abs(T - Tmax_sat) < 1e-12) {
394 HEOS.
_p = 0.5 * HEOS.
SatV->p() + 0.5 * HEOS.
SatL->p();
396 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));
398 double rhoL = _HUGE, rhoV = _HUGE;
403 HEOS.
_p = 0.5 * HEOS.
SatV->p() + 0.5 * HEOS.
SatL->p();
405 }
else if (!(HEOS.
components[0].EOS().pseudo_pure)) {
412 HEOS.
_p = 0.5 * HEOS.
SatV->p() + 0.5 * HEOS.
SatL->p();
416 CoolPropDbl rhoLanc = _HUGE, rhoVanc = _HUGE, rhoLsat = _HUGE, rhoVsat = _HUGE;
419 rhoLanc = HEOS.
components[0].ancillaries.rhoL.evaluate(HEOS.
_T);
420 HEOS.
SatL->update_TP_guessrho(HEOS.
_T, HEOS.
_p, rhoLanc);
424 rhoVanc = HEOS.
components[0].ancillaries.rhoV.evaluate(HEOS.
_T);
425 HEOS.
SatV->update_TP_guessrho(HEOS.
_T, HEOS.
_p, rhoVanc);
428 throw CoolProp::ValueError(
format(
"For pseudo-pure fluid, quality must be equal to 0 or 1. Two-phase quality is not defined"));
462 SaturationSolvers::newton_raphson_saturation NR;
463 SaturationSolvers::newton_raphson_saturation_options IO;
465 IO.bubble_point = (HEOS.
_Q < 0.5);
475 IO.imposed_variable = SaturationSolvers::newton_raphson_saturation_options::T_IMPOSED;
477 if (IO.bubble_point) {
479 NR.call(HEOS, IO.x, IO.y, IO);
482 NR.call(HEOS, IO.y, IO.x, IO);
486 HEOS.
_rhomolar = 1 / (HEOS.
_Q / IO.rhomolar_vap + (1 - HEOS.
_Q) / IO.rhomolar_liq);
496void get_Henrys_coeffs_FP(
const std::string& CAS,
double& A,
double& B,
double& C,
double& Tmin,
double& Tmax) {
498 if (CAS ==
"7440-59-7")
505 }
else if (CAS ==
"7440-01-9")
512 }
else if (CAS ==
"7440-37-1")
519 }
else if (CAS ==
"7439-90-9")
526 }
else if (CAS ==
"7440-63-3")
533 }
else if (CAS ==
"1333-74-0")
540 }
else if (CAS ==
"7727-37-9")
547 }
else if (CAS ==
"7782-44-7")
554 }
else if (CAS ==
"630-08-0")
561 }
else if (CAS ==
"124-38-9")
568 }
else if (CAS ==
"7783-06-4")
575 }
else if (CAS ==
"74-82-8")
582 }
else if (CAS ==
"74-84-0")
589 }
else if (CAS ==
"2551-62-4")
597 throw ValueError(
"Bad component in Henry's law constants");
606 auto& superanc = optsuperanc.value();
608 if (HEOS.
_p > pmax_num){
609 throw ValueError(
format(
"Pressure to PQ_flash [%0.8Lg Pa] may not be above the numerical critical point of %0.15Lg Pa", HEOS.
_p, pmax_num));
611 auto T = superanc.get_T_from_p(HEOS.
_p);
612 auto rhoL = superanc.eval_sat(T,
'D', 0);
613 auto rhoV = superanc.eval_sat(T,
'D', 1);
615 HEOS.
SatL->update_TDmolarP_unchecked(T, rhoL, p);
616 HEOS.
SatV->update_TDmolarP_unchecked(T, rhoV, p);
634 HEOS.
SatL->update_TP_guessrho(HEOS.
_TLanc, HEOS.
_p, rhoL);
635 HEOS.
SatV->update_TP_guessrho(HEOS.
_TVanc, HEOS.
_p, rhoV);
639 HEOS.
_p = HEOS.
_Q * HEOS.
SatV->p() + (1 - HEOS.
_Q) * HEOS.
SatL->p();
640 HEOS.
_T = HEOS.
_Q * HEOS.
SatV->T() + (1 - HEOS.
_Q) * HEOS.
SatL->T();
649 pmin_sat = std::max(pmin_satL, pmin_satV);
664 throw ValueError(
format(
"Pressure to PQ_flash [%6g Pa] must be in range [%8Lg Pa, %8Lg Pa]", HEOS.
_p, pmin_sat, pmax_sat));
678 double increment = 0.4;
681 for (
double omega = 1.0; omega > 0; omega -= increment) {
683 options.
omega = omega;
691 if (omega < 1.1 * increment) {
704 HEOS.
_p = HEOS.
_Q * HEOS.
SatV->p() + (1 - HEOS.
_Q) * HEOS.
SatL->p();
724 std::vector<CoolPropDbl> K = HEOS.
K;
726 if (
get_config_bool(HENRYS_LAW_TO_GENERATE_VLE_GUESSES) && std::abs(HEOS.
_Q - 1) < 1e-10) {
727 const std::vector<CoolPropFluid>& components = HEOS.
get_components();
728 std::size_t iWater = 0;
729 double p1star =
PropsSI(
"P",
"T", Tguess,
"Q", 1,
"Water");
731 std::vector<CoolPropDbl> x(y.size());
732 for (std::size_t i = 0; i < components.size(); ++i) {
733 if (components[i].CAS ==
"7732-18-5") {
737 double A, B, C, Tmin, Tmax;
739 double T_R = Tguess / 647.096, tau = 1 - T_R;
740 double k_H = p1star * exp(A / T_R + B * pow(tau, 0.355) / T_R + C * pow(T_R, -0.41) * exp(tau));
741 x[i] = y[i] * HEOS.
_p / k_H;
748 for (std::size_t i = 0; i < y.size(); ++i) {
754 K[iWater] = y[iWater] / x[iWater];
764 SaturationSolvers::newton_raphson_saturation NR;
765 SaturationSolvers::newton_raphson_saturation_options IO;
767 IO.bubble_point = (HEOS.
_Q < 0.5);
775 IO.imposed_variable = SaturationSolvers::newton_raphson_saturation_options::P_IMPOSED;
777 if (IO.bubble_point) {
779 NR.call(HEOS, IO.x, IO.y, IO);
782 NR.call(HEOS, IO.y, IO.x, IO);
795 SaturationSolvers::newton_raphson_saturation NR;
796 SaturationSolvers::newton_raphson_saturation_options IO;
799 IO.x = std::vector<CoolPropDbl>(guess.
x.begin(), guess.
x.end());
800 IO.y = std::vector<CoolPropDbl>(guess.
y.begin(), guess.
y.end());
803 IO.bubble_point =
false;
804 IO.imposed_variable = SaturationSolvers::newton_raphson_saturation_options::P_IMPOSED;
806 if (std::abs(HEOS.
Q()) < 1e-10) {
807 IO.bubble_point =
true;
808 NR.call(HEOS, IO.x, IO.y, IO);
809 }
else if (std::abs(HEOS.
Q() - 1) < 1e-10) {
810 IO.bubble_point =
false;
811 NR.call(HEOS, IO.y, IO.x, IO);
818 HEOS.
_rhomolar = 1 / (HEOS.
_Q / IO.rhomolar_vap + (1 - HEOS.
_Q) / IO.rhomolar_liq);
822 SaturationSolvers::newton_raphson_saturation NR;
823 SaturationSolvers::newton_raphson_saturation_options IO;
826 IO.x = std::vector<CoolPropDbl>(guess.
x.begin(), guess.
x.end());
827 IO.y = std::vector<CoolPropDbl>(guess.
y.begin(), guess.
y.end());
830 IO.bubble_point =
false;
831 IO.imposed_variable = SaturationSolvers::newton_raphson_saturation_options::T_IMPOSED;
834 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,
838 if (std::abs(HEOS.
Q()) < 1e-10) {
839 IO.bubble_point =
true;
840 NR.call(HEOS, IO.x, IO.y, IO);
841 }
else if (std::abs(HEOS.
Q() - 1) < 1e-10) {
842 IO.bubble_point =
false;
843 NR.call(HEOS, IO.y, IO.x, IO);
851 HEOS.
_rhomolar = 1 / (HEOS.
_Q / IO.rhomolar_vap + (1 - HEOS.
_Q) / IO.rhomolar_liq);
882 std::vector<std::pair<std::size_t, std::size_t>> intersections =
893 quality_options quality;
895 quality = SATURATED_LIQUID;
897 quality = SATURATED_VAPOR;
898 }
else if (HEOS.
_Q > 0 && HEOS.
_Q < 1) {
901 throw ValueError(
"Quality is not within 0 and 1");
904 if (quality == SATURATED_LIQUID || quality == SATURATED_VAPOR) {
909 std::vector<std::size_t> solutions;
910 for (std::vector<std::pair<std::size_t, std::size_t>>::const_iterator it = intersections.begin(); it != intersections.end(); ++it) {
912 solutions.push_back(it->first);
916 if (solutions.size() == 1) {
918 std::size_t& imax = solutions[0];
921 if (imax + 2 >= env.T.size()) {
923 }
else if (imax == 0) {
931 SaturationSolvers::newton_raphson_saturation NR;
932 SaturationSolvers::newton_raphson_saturation_options IO;
936 IO.imposed_variable = SaturationSolvers::newton_raphson_saturation_options::P_IMPOSED;
938 IO.rhomolar_vap =
CubicInterp(env.p, env.rhomolar_vap, imax - 1, imax, imax + 1, imax + 2,
static_cast<CoolPropDbl>(IO.p));
939 IO.T =
CubicInterp(env.rhomolar_vap, env.T, imax - 1, imax, imax + 1, imax + 2, IO.rhomolar_vap);
940 }
else if (other ==
iT) {
942 IO.imposed_variable = SaturationSolvers::newton_raphson_saturation_options::T_IMPOSED;
944 IO.rhomolar_vap =
CubicInterp(env.T, env.rhomolar_vap, imax - 1, imax, imax + 1, imax + 2,
static_cast<CoolPropDbl>(IO.T));
945 IO.p =
CubicInterp(env.rhomolar_vap, env.p, imax - 1, imax, imax + 1, imax + 2, IO.rhomolar_vap);
949 IO.rhomolar_liq =
CubicInterp(env.rhomolar_vap, env.rhomolar_liq, imax - 1, imax, imax + 1, imax + 2, IO.rhomolar_vap);
951 if (quality == SATURATED_VAPOR) {
952 IO.bubble_point =
false;
954 IO.x.resize(IO.y.size());
955 for (std::size_t i = 0; i < IO.x.size() - 1; ++i)
957 IO.x[i] =
CubicInterp(env.rhomolar_vap, env.x[i], imax - 1, imax, imax + 1, imax + 2, IO.rhomolar_vap);
959 IO.x[IO.x.size() - 1] = 1 - std::accumulate(IO.x.begin(), IO.x.end() - 1, 0.0);
960 NR.call(HEOS, IO.y, IO.x, IO);
962 IO.bubble_point =
true;
964 IO.y.resize(IO.x.size());
966 std::swap(IO.rhomolar_liq, IO.rhomolar_vap);
967 for (std::size_t i = 0; i < IO.y.size() - 1; ++i)
971 IO.y[i] =
CubicInterp(env.rhomolar_vap, env.x[i], imax - 1, imax, imax + 1, imax + 2, IO.rhomolar_liq);
973 IO.y[IO.y.size() - 1] = 1 - std::accumulate(IO.y.begin(), IO.y.end() - 1, 0.0);
974 NR.call(HEOS, IO.x, IO.y, IO);
976 }
else if (solutions.size() == 0) {
977 throw ValueError(
"No solution was found in PQ_flash");
979 throw ValueError(
"More than 1 solution was found in PQ_flash");
987 std::vector<std::size_t> liquid_solutions, vapor_solutions;
988 for (std::vector<std::pair<std::size_t, std::size_t>>::const_iterator it = intersections.begin(); it != intersections.end(); ++it) {
989 if (std::abs(env.Q[it->first] - 0) < 10 *
DBL_EPSILON && std::abs(env.Q[it->second] - 0) < 10 *
DBL_EPSILON) {
990 liquid_solutions.push_back(it->first);
992 if (std::abs(env.Q[it->first] - 1) < 10 *
DBL_EPSILON && std::abs(env.Q[it->second] - 1) < 10 *
DBL_EPSILON) {
993 vapor_solutions.push_back(it->first);
997 if (liquid_solutions.size() != 1 || vapor_solutions.size() != 1) {
998 throw ValueError(
format(
"Number liquid solutions [%d] or vapor solutions [%d] != 1", liquid_solutions.size(), vapor_solutions.size()));
1000 std::size_t iliq = liquid_solutions[0], ivap = vapor_solutions[0];
1002 SaturationSolvers::newton_raphson_twophase NR;
1003 SaturationSolvers::newton_raphson_twophase_options IO;
1006 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,
1013 IO.imposed_variable = SaturationSolvers::newton_raphson_twophase_options::P_IMPOSED;
1016 rhomolar_vap_sat_vap =
CubicInterp(env.p, env.rhomolar_vap, ivap - 1, ivap, ivap + 1, ivap + 2,
static_cast<CoolPropDbl>(IO.p));
1017 T_sat_vap =
CubicInterp(env.rhomolar_vap, env.T, ivap - 1, ivap, ivap + 1, ivap + 2, rhomolar_vap_sat_vap);
1018 rhomolar_liq_sat_vap =
CubicInterp(env.rhomolar_vap, env.rhomolar_liq, ivap - 1, ivap, ivap + 1, ivap + 2, rhomolar_vap_sat_vap);
1021 rhomolar_liq_sat_liq =
CubicInterp(env.p, env.rhomolar_vap, iliq - 1, iliq, iliq + 1, iliq + 2,
static_cast<CoolPropDbl>(IO.p));
1022 T_sat_liq =
CubicInterp(env.rhomolar_vap, env.T, iliq - 1, iliq, iliq + 1, iliq + 2, rhomolar_liq_sat_liq);
1023 rhomolar_vap_sat_liq =
CubicInterp(env.rhomolar_vap, env.rhomolar_liq, iliq - 1, iliq, iliq + 1, iliq + 2, rhomolar_liq_sat_liq);
1024 }
else if (other ==
iT) {
1028 IO.imposed_variable = SaturationSolvers::newton_raphson_twophase_options::T_IMPOSED;
1031 rhomolar_vap_sat_vap =
CubicInterp(env.T, env.rhomolar_vap, ivap - 1, ivap, ivap + 1, ivap + 2,
static_cast<CoolPropDbl>(IO.T));
1032 p_sat_vap =
CubicInterp(env.rhomolar_vap, env.p, ivap - 1, ivap, ivap + 1, ivap + 2, rhomolar_vap_sat_vap);
1033 rhomolar_liq_sat_vap =
CubicInterp(env.rhomolar_vap, env.rhomolar_liq, ivap - 1, ivap, ivap + 1, ivap + 2, rhomolar_vap_sat_vap);
1036 rhomolar_liq_sat_liq =
CubicInterp(env.T, env.rhomolar_vap, iliq - 1, iliq, iliq + 1, iliq + 2,
static_cast<CoolPropDbl>(IO.T));
1037 p_sat_liq =
CubicInterp(env.rhomolar_vap, env.p, iliq - 1, iliq, iliq + 1, iliq + 2, rhomolar_liq_sat_liq);
1038 rhomolar_vap_sat_liq =
CubicInterp(env.rhomolar_vap, env.rhomolar_liq, iliq - 1, iliq, iliq + 1, iliq + 2, rhomolar_liq_sat_liq);
1044 IO.rhomolar_vap = IO.beta * rhomolar_vap_sat_vap + (1 - IO.beta) * rhomolar_vap_sat_liq;
1045 IO.rhomolar_liq = IO.beta * rhomolar_liq_sat_vap + (1 - IO.beta) * rhomolar_liq_sat_liq;
1046 IO.T = IO.beta * T_sat_vap + (1 - IO.beta) * T_sat_liq;
1047 IO.p = IO.beta * p_sat_vap + (1 - IO.beta) * p_sat_liq;
1050 IO.x.resize(IO.z.size());
1051 IO.y.resize(IO.z.size());
1053 for (std::size_t i = 0; i < IO.x.size() - 1; ++i)
1055 CoolPropDbl x_sat_vap =
CubicInterp(env.rhomolar_vap, env.x[i], ivap - 1, ivap, ivap + 1, ivap + 2, rhomolar_vap_sat_vap);
1056 CoolPropDbl y_sat_vap =
CubicInterp(env.rhomolar_vap, env.y[i], ivap - 1, ivap, ivap + 1, ivap + 2, rhomolar_vap_sat_vap);
1058 CoolPropDbl x_sat_liq =
CubicInterp(env.rhomolar_vap, env.y[i], iliq - 1, iliq, iliq + 1, iliq + 2, rhomolar_liq_sat_liq);
1059 CoolPropDbl y_sat_liq =
CubicInterp(env.rhomolar_vap, env.x[i], iliq - 1, iliq, iliq + 1, iliq + 2, rhomolar_liq_sat_liq);
1061 IO.x[i] = IO.beta * x_sat_vap + (1 - IO.beta) * x_sat_liq;
1062 IO.y[i] = IO.beta * y_sat_vap + (1 - IO.beta) * y_sat_liq;
1064 IO.x[IO.x.size() - 1] = 1 - std::accumulate(IO.x.begin(), IO.x.end() - 1, 0.0);
1065 IO.y[IO.y.size() - 1] = 1 - std::accumulate(IO.y.begin(), IO.y.end() - 1, 0.0);
1080 : HEOS(HEOS), rhomolar_spec(rhomolar_spec), other(other), value(value) {
1083 double call(
double T) {
1087 Qd = (1 / rhomolar_spec - 1 / SatL.
rhomolar()) / (1 / SatV.rhomolar() - 1 / SatL.
rhomolar());
1093 } resid(HEOS, rhomolar_spec, other, value);
1101 Tmin_sat = std::max(Tmin_satL, Tmin_satV) - 1e-13;
1119 : HEOS(HEOS), rhomolar(rhomolar), value(value), other(other), Tmin(Tmin), Tmax(Tmax) {
1123 double call(
double T) {
1128 return (eos - value) / value;
1134 double deriv(
double T) {
1140 double second_deriv(
double T) {
1146 bool input_not_in_range(
double T) {
1147 return (T < Tmin || T > Tmax);
1154 shared_ptr<HelmholtzEOSMixtureBackend> Sat;
1188 y_solid = (yV - yL) / (1 / rhoVtriple - 1 / rhoLtriple) * (1 / HEOS.
_rhomolar - 1 / rhoLtriple) + yL;
1190 if (value < y_solid) {
1199 for (
int i_try = 0; i_try < 7; i_try++)
1221 optionsD.
omega /= 2;
1223 if (i_try >= 6){
throw;}
1228 if (value > Sat->keyed_output(other)) {
1229 solver_resid resid(&HEOS, HEOS.
_rhomolar, value, other, Sat->keyed_output(
iT), HEOS.
Tmax() * 1.5);
1231 HEOS.
_T =
Halley(resid, 0.5 * (Sat->keyed_output(
iT) + HEOS.
Tmax() * 1.5), 1e-10, 100);
1257 HEOS.
_Q = (1 / HEOS.
_rhomolar - 1 / HEOS.
SatL->rhomolar()) / (1 / HEOS.
SatV->rhomolar() - 1 / HEOS.
SatL->rhomolar());
1258 HEOS.
_T = HEOS.
SatL->T();
1294 solver_resid resid(&HEOS, HEOS.
_rhomolar, value, other, TVtriple, HEOS.
Tmax() * 1.5);
1335 solver_resid resid(&HEOS, HEOS.
_rhomolar, value, other, TLtriple, HEOS.
Tmax() * 1.5);
1357 double A[2][2], B[2][2];
1373 throw ValueError(
"other is invalid in HSU_P_flash_singlephase_Newton");
1378 bool failed =
false;
1379 CoolPropDbl omega = 1.0, f2, df2_dtau, df2_ddelta;
1381 while (worst_error > 1e-6 && failed ==
false) {
1397 CoolPropDbl f1 = delta / tau * (1 + delta * dar_ddelta) - p / (rhoc * R * Tc);
1398 CoolPropDbl df1_dtau = (1 + delta * dar_ddelta) * (-delta / tau / tau) + delta / tau * (delta * d2ar_ddelta_dtau);
1399 CoolPropDbl df1_ddelta = (1.0 / tau) * (1 + 2.0 * delta * dar_ddelta + delta * delta * d2ar_ddelta2);
1402 f2 = (1 + delta * dar_ddelta) + tau * (da0_dtau + dar_dtau) - tau * y / (R * Tc);
1403 df2_dtau = delta * d2ar_ddelta_dtau + da0_dtau + dar_dtau + tau * (d2a0_dtau2 + d2ar_dtau2) - y / (R * Tc);
1404 df2_ddelta = (dar_ddelta + delta * d2ar_ddelta2) + tau * (d2a0_ddelta_dtau + d2ar_ddelta_dtau);
1408 f2 = tau * (da0_dtau + dar_dtau) - ar - a0 - y / R;
1409 df2_dtau = tau * (d2a0_dtau2 + d2ar_dtau2) + (da0_dtau + dar_dtau) - dar_dtau - da0_dtau;
1410 df2_ddelta = tau * (d2a0_ddelta_dtau + d2ar_ddelta_dtau) - dar_ddelta - da0_ddelta;
1414 throw ValueError(
"other variable in HSU_P_flash_singlephase_Newton is invalid");
1419 A[0][1] = df1_ddelta;
1421 A[1][1] = df2_ddelta;
1426 tau -= omega * (B[0][0] * f1 + B[0][1] * f2);
1427 delta -= omega * (B[1][0] * f1 + B[1][1] * f2);
1429 if (std::abs(f1) > std::abs(f2))
1430 worst_error = std::abs(f1);
1432 worst_error = std::abs(f2);
1435 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()));
1440 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()));
1449 throw ValueError(
"value for p in HSU_P_flash_singlephase_Brent is invalid");
1452 throw ValueError(
"value for other in HSU_P_flash_singlephase_Brent is invalid");
1461 CoolPropDbl eos0, eos1, rhomolar, rhomolar0, rhomolar1;
1487 double call(
double T) {
1489 if (iter < 2 || std::abs(rhomolar1 / rhomolar0 - 1) > 0.05) {
1503 if (verbosity > 0 && iter == 0){
1504 std::cout <<
format(
"T: %0.15g rho: %0.15g eos: %0.15g", T, rhomolar, eos);
1513 rhomolar0 = rhomolar;
1514 }
else if (iter == 1) {
1516 rhomolar1 = rhomolar;
1520 rhomolar0 = rhomolar1;
1521 rhomolar1 = rhomolar;
1527 double deriv(
double T) {
1530 double second_deriv(
double T) {
1533 bool input_not_in_range(
double x) {
1534 return (x < Tmin || x > Tmax);
1537 solver_resid resid(&HEOS, HEOS.
_p, value, other, Tmin, Tmax);
1545 std::vector<std::vector<double>> J = {{-1.0, -1.0},{-1.0, -1.0}};
1553 std::vector<double> call(
const std::vector<double>&x)
override{
1554 double T = x[0], rhomolar = x[1];
1561 return {(HEOS->
p()-p)/p, HEOS->
keyed_output(other) - value};
1563 std::vector<std::vector<double>> Jacobian(
const std::vector<double>&)
override{
1568 resid_2D solver_resid2d(&HEOS, HEOS.
_p, value, other, Tmin, Tmax);
1571 double resid_Tmin = resid.call(Tmin);
1572 double rhomolar_Tmin = HEOS.
rhomolar();
1574 double resid_Tmax = resid.call(Tmax);
1575 double rhomolar_Tmax = HEOS.
rhomolar();
1578 bool use_min = std::abs(resid_Tmin) < std::abs(resid_Tmax);
1579 double Tstart = use_min ? Tmin : Tmax;
1580 double rhomolarstart = use_min ? rhomolar_Tmin : rhomolar_Tmax;
1584 resid.verbosity = 1;
1586 if (resid_Tmin*resid_Tmax < 0){
1594 boost::math::uintmax_t max_iter = 100;
1596 auto f = [&resid](
const double T){
return resid.call(T); };
1600 auto [l, r] = toms748_solve(f, Tmin, Tmax, resid_Tmin, resid_Tmax, boost::math::tools::eps_tolerance<double>(44), max_iter);
1611 Halley(resid, Tstart, 1e-12, 100);
1613 throw ValueError(
"Halley's method was unable to find a solution in HSU_P_flash_singlephase_Brent");
1622 if (0.95*p_critical_ > HEOS.
_p || HEOS.
_p > p_critical_){
1626 std::cout << resid.errstring << std::endl;
1628 std::vector<double> x0 = {Tstart, rhomolarstart};
1631 throw ValueError(
"2D Newton method was unable to find a solution in HSU_P_flash_singlephase_Brent");
1639 std::cout << resid.errstring << std::endl;
1649 if (eos1 > eos0 && value > eos1) {
1651 format(
"HSU_P_flash_singlephase_Brent could not find a solution because %s [%Lg %s] is above the maximum value of %0.12Lg %s",
1652 name.c_str(), value, units.c_str(), eos1, units.c_str()));
1654 if (eos1 > eos0 && value < eos0) {
1656 format(
"HSU_P_flash_singlephase_Brent could not find a solution because %s [%Lg %s] is below the minimum value of %0.12Lg %s",
1657 name.c_str(), value, units.c_str(), eos0, units.c_str()));
1666 bool saturation_called =
false;
1694 Tmax = 1.5 * HEOS.
Tmax();
1702 auto& superanc = optsuperanc.value();
1704 if (HEOS.
_p > pmax_num){
1705 throw ValueError(
format(
"Pressure to PQ_flash [%0.8Lg Pa] may not be above the numerical critical point of %0.15Lg Pa", HEOS.
_p, pmax_num));
1707 Tmin = superanc.get_T_from_p(HEOS.
_p);
1711 if (saturation_called) {
1712 Tmin = HEOS.
SatV->T();
1725 Tmin = HEOS.
Tmin() - 1e-3;
1731 auto& superanc = optsuperanc.value();
1733 if (HEOS.
_p > pmax_num){
1734 throw ValueError(
format(
"Pressure to PQ_flash [%0.8Lg Pa] may not be above the numerical critical point of %0.15Lg Pa", HEOS.
_p, pmax_num));
1736 Tmax = superanc.get_T_from_p(HEOS.
_p);
1741 if (saturation_called) {
1742 Tmax = HEOS.
SatL->T();
1753 Tmax = 1.5 * HEOS.
Tmax();
1758 Tmin = HEOS.
Tmin() - 1e-3;
1768 }
catch (std::exception& e) {
1769 throw ValueError(
format(
"unable to solve 1phase PY flash with Tmin=%Lg, Tmax=%Lg due to error: %s", Tmin, Tmax, e.what()));
1779 std::size_t iclosest;
1788 throw ValueError(
"two-phase solution for Y");
1792 throw ValueError(
"phase envelope must be built to carry out HSU_P_flash for mixture");
1806 : HEOS(HEOS), T(T), value(value), other(other) {}
1807 double call(
double rhomolar) {
1812 double deriv(
double rhomolar) {
1815 double second_deriv(
double rhomolar) {
1819 solver_resid resid(&HEOS, T, value, other);
1824 if (HEOS.
_T > T_critical_) {
1853 Brent(resid, rhoc, rhomin, LDBL_EPSILON, 1e-9, 100);
1854 }
else if (y < yc) {
1872 if (step_count > 30) {
1873 throw ValueError(
format(
"Even by increasing rhoc, not able to bound input; input %Lg is not in range %Lg,%Lg", y, yc, ymin));
1877 Brent(resid, rhomin, rhoc, LDBL_EPSILON, 1e-9, 100);
1879 throw ValueError(
format(
"input %Lg is not in range %Lg,%Lg,%Lg", y, yc, ymin));
1917 CoolPropDbl rhomolar_guess = (rhomelt - rhoL) / (ymelt - yL) * (y - yL) + rhoL;
1920 Halley(resid, rhomolar_guess, 1e-8, 100);
1922 Secant(resid, rhomolar_guess, 0.0001 * rhomolar_guess, 1e-12, 100);
1931 Halley(resid, 0.5 * (rhomin + rhoV), 1e-8, 100);
1934 Brent(resid, rhomin, rhoV, LDBL_EPSILON, 1e-12, 100);
1940 throw ValueError(
format(
"phase to solver_for_rho_given_T_oneof_HSU is invalid"));
1951 if (HEOS.
_T < T_critical_)
1981 Q = (1 / HEOS.
rhomolar() - 1 / HEOS1.
SatL->rhomolar()) / (1 / HEOS1.
SatV->rhomolar() - 1 / HEOS1.
SatL->rhomolar());
1984 Q = (HEOS.
smolar() - HEOS1.
SatL->smolar()) / (HEOS1.
SatV->smolar() - HEOS1.
SatL->smolar());
1987 Q = (HEOS.
hmolar() - HEOS1.
SatL->hmolar()) / (HEOS1.
SatV->hmolar() - HEOS1.
SatL->hmolar());
1990 Q = (HEOS.
umolar() - HEOS1.
SatL->umolar()) / (HEOS1.
SatV->umolar() - HEOS1.
SatL->umolar());
2002 HEOS.
_p = HEOS.
_Q * HEOS1.
SatV->p() + (1 - HEOS.
_Q) * HEOS1.
SatL->p();
2008 throw ValueError(
format(
"Temperature specified is not the imposed phase region."));
2009 }
else if (HEOS.
_T > T_critical_ && HEOS.
_T > HEOS.
components[0].EOS().Ttriple) {
2073 : HEOS(HEOS), hmolar(hmolar_spec), smolar(smolar_spec), Qs(_HUGE){};
2074 double call(
double T) {
2078 Qs = (smolar - SatL.
smolar()) / (SatV.smolar() - SatL.
smolar());
2084 } resid(HEOS, hmolar_spec, smolar_spec);
2092 Tmin_sat = std::max(Tmin_satL, Tmin_satV) - 1e-13;
2101 double resid = 9e30, resid_old = 9e30;
2107 r(0) = HEOS.
hmolar() - hmolar_spec;
2108 r(1) = HEOS.
smolar() - smolar_spec;
2114 Eigen::Vector2d v = J.colPivHouseholderQr().solve(-r);
2115 bool good_solution =
false;
2116 double tau0 = HEOS.
tau(), delta0 = HEOS.
delta();
2119 for (
double frac = 1.0; frac > 0.001; frac /= 2) {
2122 double tau_new = tau0 + options.
omega * frac * v(0);
2123 double delta_new = delta0 + options.
omega * frac * v(1);
2124 double T_new = reducing.
T / tau_new;
2125 double rhomolar_new = delta_new * reducing.
rhomolar;
2129 if (resid > resid_old) {
2130 throw ValueError(
format(
"residual not decreasing; frac: %g, resid: %g, resid_old: %g", frac, resid, resid_old));
2132 good_solution =
true;
2139 if (!good_solution) {
2144 throw ValueError(
format(
"HS_flash_singlephase took too many iterations; residual is %g; prior was %g", resid, resid_old));
2146 }
while (std::abs(resid) > 1e-9);
2150 double logp = ((double)rand() / (double)RAND_MAX) * (log(HEOS.
pmax()) - log(HEOS.
p_triple())) + log(HEOS.
p_triple());
2151 T = ((double)rand() / (double)RAND_MAX) * (HEOS.
Tmax() - HEOS.
Ttriple()) + HEOS.
Ttriple();
2164 : HEOS(HEOS), hmolar(hmolar_spec), smolar(smolar_spec){};
2165 double call(
double T) {
2167 double r = HEOS.
hmolar() - hmolar;
2170 } resid(HEOS, hmolar, smolar);
2173 bool good_Tmin =
false;
2178 rmin = resid.call(Tmin);
2183 if (Tmin > HEOS.
Tmax()) {
2186 }
while (!good_Tmin);
2189 bool good_Tmax =
false;
2190 double Tmax = HEOS.
Tmax() * 1.01;
2194 rmax = resid.call(Tmax);
2202 }
while (!good_Tmax);
2203 if (rmin * rmax > 0 && std::abs(rmax) < std::abs(rmin)) {
2209#if defined(ENABLE_CATCH)
2211TEST_CASE(
"PD with T very large should yield error",
"[PDflash]") {
2213 double Tc = HEOS->T_critical();
2215 CHECK_THROWS(HEOS->update(
DmassP_INPUTS, 2, 5 * HEOS->p()));
2218TEST_CASE(
"Stability testing",
"[stability]") {
2219 shared_ptr<HelmholtzEOSMixtureBackend> HEOS(
new HelmholtzEOSMixtureBackend(
strsplit(
"n-Propane&n-Butane&n-Pentane&n-Hexane",
'&')));
2220 std::vector<double> z(4);
2225 HEOS->set_mole_fractions(z);
2228 double TL = HEOS->T();
2231 double TV = HEOS->T();
2233 SECTION(
"Liquid (feed is stable)") {
2234 StabilityRoutines::StabilityEvaluationClass stability_tester(*HEOS);
2235 for (
double T = TL - 1; T >= 100; T -= 1) {
2236 stability_tester.set_TP(T, 101325);
2238 CHECK_NOTHROW(stability_tester.is_stable());
2241 SECTION(
"Vapor (feed is stable)") {
2242 StabilityRoutines::StabilityEvaluationClass stability_tester(*HEOS);
2243 for (
double T = TV + 1; T <= 500; T += 1) {
2244 stability_tester.set_TP(T, 101325);
2246 CHECK_NOTHROW(stability_tester.is_stable());
2249 SECTION(
"Two-phase (feed is unstable)") {
2250 StabilityRoutines::StabilityEvaluationClass stability_tester(*HEOS);
2251 stability_tester.set_TP((TV + TL) / 2.0, 101325);
2252 CHECK(stability_tester.is_stable() ==
false);
2256TEST_CASE(
"Test critical points for methane + H2S",
"[critical_points]") {
2257 shared_ptr<HelmholtzEOSMixtureBackend> HEOS(
new HelmholtzEOSMixtureBackend(
strsplit(
"Methane&H2S",
'&')));
2259 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};
2260 int Npts[] = {2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 1, 1, 1, 1};
2261 int imax =
sizeof(zz) /
sizeof(
double);
2263 for (
int i = 0; i < imax; ++i) {
2265 std::vector<double> z(2);
2268 HEOS->set_mole_fractions(z);
2270 std::vector<CriticalState> pts = HEOS->all_critical_points();
2271 CHECK(pts.size() == Npts[i]);
2275TEST_CASE(
"Test critical points for nitrogen + ethane with HEOS",
"[critical_points]") {
2276 shared_ptr<HelmholtzEOSMixtureBackend> HEOS(
new HelmholtzEOSMixtureBackend(
strsplit(
"Nitrogen&Ethane",
'&')));
2277 std::vector<double> zz =
linspace(0.001, 0.999, 21);
2278 int failure_count = 0;
2279 for (
int i = 0; i < static_cast<std::size_t>(zz.size()); ++i) {
2281 std::vector<double> z(2);
2284 HEOS->set_mole_fractions(z);
2286 std::vector<CriticalState> pts;
2288 pts = HEOS->all_critical_points();
2290 catch(std::exception& e){
2296 CHECK(failure_count < 10);
2299TEST_CASE(
"Test critical points for nitrogen + ethane with PR",
"[critical_points]") {
2300 shared_ptr<PengRobinsonBackend> HEOS(
new PengRobinsonBackend(
strsplit(
"Nitrogen&Ethane",
'&')));
2301 HEOS->set_binary_interaction_double(0, 1,
"kij", 0.0407);
2302 std::vector<double> zz =
linspace(0.001, 0.999, 21);
2303 for (
int i = 0; i < static_cast<std::size_t>(zz.size()); ++i) {
2305 std::vector<double> z(2);
2308 HEOS->set_mole_fractions(z);
2310 std::vector<CriticalState> pts;
2311 CHECK_NOTHROW(pts = HEOS->all_critical_points());