9#include "boost/math/tools/toms748_solve.hpp"
41 return std::isfinite(v);
48 std::vector<PropertySpec> ps = {
72 std::vector<PropertySpec> ps = {
93 std::vector<PropertySpec> ps = {
130 constexpr double kT_R1R3 = 623.15;
131 constexpr double kP_R1R3_lo_MPa = 16.529;
132 const double p_lo_clamped = std::max(p_lo, kP_R1R3_lo_MPa * 1.0e6);
133 if (!(p_lo_clamped < p_hi)) {
134 throw std::invalid_argument(
"build_h_R1R3_curve: p range does not overlap IF97 R1/R3 isotherm's validity (p > 16.529 MPa)");
136 constexpr std::size_t n_knots = 64;
137 std::vector<double> p_knots(n_knots);
138 std::vector<double> h_knots(n_knots);
139 const double log_p_lo = std::log(p_lo_clamped);
140 const double log_p_hi = std::log(p_hi);
141 for (std::size_t k = 0; k < n_knots; ++k) {
142 const double p = std::exp(log_p_lo +
static_cast<double>(k) * (log_p_hi - log_p_lo) /
static_cast<double>(n_knots - 1));
145 h_knots[k] = heos.
hmass();
151 constexpr double kP_B23_lo_MPa = 16.529;
152 constexpr double kP_B23_hi_MPa = 100.0;
153 const double p_lo_clamped = std::max(p_lo, kP_B23_lo_MPa * 1.0e6);
154 const double p_hi_clamped = std::min(p_hi, kP_B23_hi_MPa * 1.0e6);
155 if (!(p_lo_clamped < p_hi_clamped)) {
156 throw std::invalid_argument(
"build_h_B23_curve: p range does not overlap IF97 B23 curve's [16.529, 100] MPa validity");
158 constexpr std::size_t n_knots = 64;
159 std::vector<double> p_knots(n_knots);
160 std::vector<double> h_knots(n_knots);
161 const double log_p_lo = std::log(p_lo_clamped);
162 const double log_p_hi = std::log(p_hi_clamped);
163 for (std::size_t k = 0; k < n_knots; ++k) {
164 const double p = std::exp(log_p_lo +
static_cast<double>(k) * (log_p_hi - log_p_lo) /
static_cast<double>(n_knots - 1));
165 const double T_B23 = IF97::Region23_p(p);
168 h_knots[k] = heos.
hmass();
184double solve_T_from_h_toms748(
::CoolProp::AbstractState& s,
double p,
double h_target,
double T_lo,
double T_hi,
bool* ok =
nullptr) {
185 auto resid = [&s, p, h_target](
double T) ->
double {
188 return s.
hmass() - h_target;
195 const double r_lo = resid(T_lo);
196 const double r_hi = resid(T_hi);
197 if (!std::isfinite(r_lo) || !std::isfinite(r_hi) || r_lo * r_hi > 0.0) {
204 return (std::abs(r_lo) < std::abs(r_hi)) ? T_lo : T_hi;
206 std::uintmax_t max_iter = 30;
207 const auto bracket = boost::math::tools::toms748_solve(resid, T_lo, T_hi, r_lo, r_hi, boost::math::tools::eps_tolerance<double>(40), max_iter);
211 return 0.5 * (bracket.first + bracket.second);
219 constexpr double kT_R1R3 = 623.15;
220 constexpr double kP_R1R3_lo_MPa = 16.529;
221 const double p_lo_clamped = std::max(p_lo, kP_R1R3_lo_MPa * 1.0e6);
222 if (!(p_lo_clamped < p_hi)) {
223 throw std::invalid_argument(
"build_s_R1R3_curve: p range does not overlap IF97 R1/R3 isotherm's validity (p > 16.529 MPa)");
225 constexpr std::size_t n_knots = 64;
226 std::vector<double> p_knots(n_knots);
227 std::vector<double> s_knots(n_knots);
228 const double log_p_lo = std::log(p_lo_clamped);
229 const double log_p_hi = std::log(p_hi);
230 for (std::size_t k = 0; k < n_knots; ++k) {
231 const double p = std::exp(log_p_lo +
static_cast<double>(k) * (log_p_hi - log_p_lo) /
static_cast<double>(n_knots - 1));
234 s_knots[k] = heos.
smass();
240 constexpr double kP_B23_lo_MPa = 16.529;
241 constexpr double kP_B23_hi_MPa = 100.0;
242 const double p_lo_clamped = std::max(p_lo, kP_B23_lo_MPa * 1.0e6);
243 const double p_hi_clamped = std::min(p_hi, kP_B23_hi_MPa * 1.0e6);
244 if (!(p_lo_clamped < p_hi_clamped)) {
245 throw std::invalid_argument(
"build_s_B23_curve: p range does not overlap IF97 B23 curve's [16.529, 100] MPa validity");
247 constexpr std::size_t n_knots = 64;
248 std::vector<double> p_knots(n_knots);
249 std::vector<double> s_knots(n_knots);
250 const double log_p_lo = std::log(p_lo_clamped);
251 const double log_p_hi = std::log(p_hi_clamped);
252 for (std::size_t k = 0; k < n_knots; ++k) {
253 const double p = std::exp(log_p_lo +
static_cast<double>(k) * (log_p_hi - log_p_lo) /
static_cast<double>(n_knots - 1));
254 const double T_B23 = IF97::Region23_p(p);
257 s_knots[k] = heos.
smass();
265double solve_T_from_s_toms748(
::CoolProp::AbstractState& s,
double p,
double s_target,
double T_lo,
double T_hi,
bool* ok =
nullptr) {
266 auto resid = [&s, p, s_target](
double T) ->
double {
269 return s.
smass() - s_target;
274 const double r_lo = resid(T_lo);
275 const double r_hi = resid(T_hi);
276 if (!std::isfinite(r_lo) || !std::isfinite(r_hi) || r_lo * r_hi > 0.0) {
280 return (std::abs(r_lo) < std::abs(r_hi)) ? T_lo : T_hi;
282 std::uintmax_t max_iter = 30;
283 const auto bracket = boost::math::tools::toms748_solve(resid, T_lo, T_hi, r_lo, r_hi, boost::math::tools::eps_tolerance<double>(40), max_iter);
287 return 0.5 * (bracket.first + bracket.second);
293 const std::size_t NT = opts.
NT;
294 const std::size_t NR = opts.
NR;
295 const std::int32_t rank = opts.
rank;
296 const std::optional<double> p_min_override = opts.
p_min;
304 const double T_min_eos = std::max(heos.
Ttriple(), heos.
Tmin());
305 const double T_max_eos = heos.
Tmax();
317 const bool source_is_if97 = (heos.
backend_name() ==
"IF97Backend");
318 constexpr double kNC_p_lo_mult = 0.9;
319 constexpr double kNC_p_hi_mult = 1.0 - 1.0e-10;
320 constexpr double kNC_sup_p_lo_mult = 1.0 + 1.0e-10;
321 constexpr double kNC_sup_p_hi_mult = 1.1;
322 const bool nc_enabled = !source_is_if97;
323 const double p_nc_lo = nc_enabled ? kNC_p_lo_mult * pc : 0.0;
324 const double p_nc_hi = nc_enabled ? kNC_p_hi_mult * pc : 0.0;
325 const double p_nc_sup_lo = nc_enabled ? kNC_sup_p_lo_mult * pc : 0.0;
326 const double p_nc_sup_hi = nc_enabled ? kNC_sup_p_hi_mult * pc : 0.0;
331 const double p_max_sub = nc_enabled ? std::min(p_max_sub_raw, p_nc_lo) : p_max_sub_raw;
332 const double p_min_sup = nc_enabled ? std::max(p_min_sup_raw, p_nc_sup_hi) : p_min_sup_raw;
346 if (p_min < p_max_sub) {
350 spec.
regions.emplace_back(axis, std::move(lo), std::move(hi));
356 if (p_min < p_max_sub) {
360 spec.
regions.emplace_back(axis, std::move(lo), std::move(hi));
368 if (nc_enabled && p_nc_lo < p_nc_hi) {
373 spec.
regions.emplace_back(axis, std::move(lo), std::move(hi));
379 spec.
regions.emplace_back(axis, std::move(lo), std::move(hi));
393 if (nc_enabled && p_nc_sup_lo < p_nc_sup_hi) {
397 spec.
regions.emplace_back(axis, std::move(lo), std::move(hi));
423 constexpr double kP_B23_hi = 100.0e6;
424 if (source_is_if97 && p_min_sup < kP_B23_hi) {
426 const double p_split_hi = std::min(p_max_sup, kP_B23_hi);
438 auto hi = build_h_R1R3_curve(heos, p_min_sup, p_split_hi);
439 spec.
regions.emplace_back(axis, std::move(lo), std::move(hi));
446 auto lo = build_h_R1R3_curve(heos, p_min_sup, p_split_hi);
447 auto hi = build_h_B23_curve(heos, p_min_sup, p_split_hi);
448 spec.
regions.emplace_back(axis, std::move(lo), std::move(hi));
453 auto lo = build_h_B23_curve(heos, p_min_sup, p_split_hi);
455 spec.
regions.emplace_back(axis, std::move(lo), std::move(hi));
460 if (p_max_sup > kP_B23_hi) {
464 spec.
regions.emplace_back(axis, std::move(lo), std::move(hi));
488 const double kT_R5_lo = std::nextafter(1073.15, 2273.15);
489 constexpr double kT_R5_hi = 2273.15;
490 constexpr double kP_R5_hi = 50.0e6;
499 constexpr double kP_R5_lo = 700.0;
500 if (kP_R5_lo < kP_R5_hi) {
501 const double p_r5_hi = std::min(p_max_sup, kP_R5_hi);
509 spec.
regions.emplace_back(axis, std::move(lo), std::move(hi));
511 }
else if (p_min_sup < p_max_sup) {
515 spec.
regions.emplace_back(axis, std::move(lo), std::move(hi));
535 if (source_is_if97) {
569 bool single_phase =
false;
570 bool seeded_via_hmass =
true;
578 seeded_via_hmass =
false;
590 if (!seeded_via_hmass) {
612 const double kR5_T_lo = std::nextafter(1073.15, 2273.15);
613 constexpr double kR5_T_hi = 2273.15;
614 constexpr double kR5_P_hi = 50.0e6;
615 constexpr double kH_R3_vs_R5_split = 3.0e6;
616 constexpr double kP_B23_lo_Pa = 16.529e6;
617 constexpr double kP_B23_hi_Pa = 100.0e6;
619 bool resolved =
false;
622 if (h >= kH_R3_vs_R5_split && p <= kR5_P_hi) {
624 const double T_R5 = solve_T_from_h_toms748(s, p, h, kR5_T_lo, kR5_T_hi, &ok);
639 if (!(p >= kP_B23_lo_Pa && p <= kP_B23_hi_Pa)) {
641 "ph_subcritical IF97 sampling: neither R5 (h<3MJ/kg or p>50MPa) nor R3 (p outside [16.529, 100] MPa) "
642 "fallback applies — HmassP backward seed failed and no bracket is defined");
644 const double T_R3_lo = 623.15;
645 const double T_R3_hi = IF97::Region23_p(p);
646 if (!(T_R3_lo < T_R3_hi)) {
650 const double T_R3 = solve_T_from_h_toms748(s, p, h, T_R3_lo, T_R3_hi, &ok);
662 const double T_seed = s.
T();
663 constexpr double kIF97_T_min = 273.16;
664 constexpr double kIF97_T_max = 2273.15;
665 const double T_lo = std::max(T_seed - 0.5, kIF97_T_min);
666 const double T_hi = std::min(T_seed + 0.5, kIF97_T_max);
669 const double T_polished = solve_T_from_h_toms748(s, p, h, T_lo, T_hi, &ok);
722 throw std::invalid_argument(
"ph_subcritical: unsupported output property");
730 const std::size_t NT = opts.
NT;
731 const std::size_t NR = opts.
NR;
732 const std::int32_t rank = opts.
rank;
733 const std::optional<double> p_min_override = opts.
p_min;
742 const double T_min_eos = std::max(heos.
Ttriple(), heos.
Tmin());
743 const double T_max_eos = heos.
Tmax();
746 const bool source_is_if97 = (heos.
backend_name() ==
"IF97Backend");
747 constexpr double kNC_p_lo_mult = 0.9;
748 constexpr double kNC_p_hi_mult = 1.0 - 1.0e-10;
749 constexpr double kNC_sup_p_lo_mult = 1.0 + 1.0e-10;
750 constexpr double kNC_sup_p_hi_mult = 1.1;
751 const bool nc_enabled = !source_is_if97;
752 const double p_nc_lo = nc_enabled ? kNC_p_lo_mult * pc : 0.0;
753 const double p_nc_hi = nc_enabled ? kNC_p_hi_mult * pc : 0.0;
754 const double p_nc_sup_lo = nc_enabled ? kNC_sup_p_lo_mult * pc : 0.0;
755 const double p_nc_sup_hi = nc_enabled ? kNC_sup_p_hi_mult * pc : 0.0;
756 const double p_max_sub = nc_enabled ? std::min(p_max_sub_raw, p_nc_lo) : p_max_sub_raw;
757 const double p_min_sup = nc_enabled ? std::max(p_min_sup_raw, p_nc_sup_hi) : p_min_sup_raw;
768 if (p_min < p_max_sub) {
772 spec.
regions.emplace_back(axis, std::move(lo), std::move(hi));
775 if (p_min < p_max_sub) {
779 spec.
regions.emplace_back(axis, std::move(lo), std::move(hi));
782 if (nc_enabled && p_nc_lo < p_nc_hi) {
787 spec.
regions.emplace_back(axis, std::move(lo), std::move(hi));
793 spec.
regions.emplace_back(axis, std::move(lo), std::move(hi));
797 if (nc_enabled && p_nc_sup_lo < p_nc_sup_hi) {
801 spec.
regions.emplace_back(axis, std::move(lo), std::move(hi));
806 constexpr double kP_B23_hi = 100.0e6;
807 if (source_is_if97 && p_min_sup < kP_B23_hi) {
808 const double p_split_hi = std::min(p_max_sup, kP_B23_hi);
813 auto hi = build_s_R1R3_curve(heos, p_min_sup, p_split_hi);
814 spec.
regions.emplace_back(axis, std::move(lo), std::move(hi));
819 auto lo = build_s_R1R3_curve(heos, p_min_sup, p_split_hi);
820 auto hi = build_s_B23_curve(heos, p_min_sup, p_split_hi);
821 spec.
regions.emplace_back(axis, std::move(lo), std::move(hi));
826 auto lo = build_s_B23_curve(heos, p_min_sup, p_split_hi);
828 spec.
regions.emplace_back(axis, std::move(lo), std::move(hi));
831 if (p_max_sup > kP_B23_hi) {
835 spec.
regions.emplace_back(axis, std::move(lo), std::move(hi));
839 const double kT_R5_lo = std::nextafter(1073.15, 2273.15);
840 constexpr double kT_R5_hi = 2273.15;
841 constexpr double kP_R5_hi = 50.0e6;
842 constexpr double kP_R5_lo = 700.0;
843 if (kP_R5_lo < kP_R5_hi) {
844 const double p_r5_hi = std::min(p_max_sup, kP_R5_hi);
848 spec.
regions.emplace_back(axis, std::move(lo), std::move(hi));
850 }
else if (p_min_sup < p_max_sup) {
854 spec.
regions.emplace_back(axis, std::move(lo), std::move(hi));
859 if (source_is_if97) {
876 bool single_phase =
false;
877 bool seeded_via_smass =
true;
880 phase0 = s_state.
phase();
885 seeded_via_smass =
false;
893 if (!seeded_via_smass) {
894 const double kR5_T_lo = std::nextafter(1073.15, 2273.15);
895 constexpr double kR5_T_hi = 2273.15;
896 constexpr double kR5_P_hi = 50.0e6;
897 constexpr double kS_R3_vs_R5_split = 5.9e3;
898 constexpr double kP_B23_lo_Pa = 16.529e6;
899 constexpr double kP_B23_hi_Pa = 100.0e6;
901 bool resolved =
false;
902 if (s_val >= kS_R3_vs_R5_split && p <= kR5_P_hi) {
904 const double T_R5 = solve_T_from_s_toms748(s_state, p, s_val, kR5_T_lo, kR5_T_hi, &ok);
911 if (!(p >= kP_B23_lo_Pa && p <= kP_B23_hi_Pa)) {
913 "ps_subcritical IF97 sampling: neither R5 (s<5.9kJ/kg/K or p>50MPa) nor R3 (p outside [16.529, 100] MPa) "
914 "fallback applies — PSmass backward seed failed and no bracket is defined");
916 const double T_R3_lo = 623.15;
917 const double T_R3_hi = IF97::Region23_p(p);
918 if (!(T_R3_lo < T_R3_hi)) {
922 const double T_R3 = solve_T_from_s_toms748(s_state, p, s_val, T_R3_lo, T_R3_hi, &ok);
930 const double T_seed = s_state.
T();
931 constexpr double kIF97_T_min = 273.16;
932 constexpr double kIF97_T_max = 2273.15;
933 const double T_lo = std::max(T_seed - 0.5, kIF97_T_min);
934 const double T_hi = std::min(T_seed + 0.5, kIF97_T_max);
937 const double T_polished = solve_T_from_s_toms748(s_state, p, s_val, T_lo, T_hi, &ok);
968 return s_state.
hmass();
970 return s_state.
umass();
978 throw std::invalid_argument(
"ps_subcritical: unsupported output property");
990std::unique_ptr<region::CubicSplineCurve> pt_T_floor_curve_(
::CoolProp::AbstractState& heos,
double p_min,
double p_max,
double T_min_eos,
992 const double log_p_min = std::log(p_min);
993 const double log_p_max = std::log(p_max);
994 std::vector<double> p_knots(sbopts.
n_knots);
995 std::vector<double> y(sbopts.
n_knots);
997 for (std::size_t k = 0; k < sbopts.
n_knots; ++k) {
998 const double p = std::exp(log_p_min +
static_cast<double>(k) * (log_p_max - log_p_min) /
static_cast<double>(sbopts.
n_knots - 1));
1000 double T_found = std::nan(
"");
1001 for (std::size_t s = 0; s < walk_steps; ++s) {
1002 const double T_try = T_min_eos + 0.5 *
static_cast<double>(s);
1015 if (!std::isfinite(T_found)) {
1016 throw std::runtime_error(
"pt preset: T-floor unreachable within " + std::to_string(walk_steps / 2) +
" K of T_min_eos");
1025 const std::size_t NT = opts.
NT;
1026 const std::size_t NR = opts.
NR;
1027 const std::int32_t rank = opts.
rank;
1028 const std::optional<double> p_min_override = opts.
p_min;
1035 const double T_min_eos = std::max(heos.
Ttriple(), heos.
Tmin());
1036 const double T_max_eos = heos.
Tmax();
1040 const bool source_is_if97 = (heos.
backend_name() ==
"IF97Backend");
1041 constexpr double kNC_p_lo_mult = 0.9;
1042 constexpr double kNC_p_hi_mult = 1.0 - 1.0e-10;
1043 constexpr double kNC_sup_p_lo_mult = 1.0 + 1.0e-10;
1044 constexpr double kNC_sup_p_hi_mult = 1.1;
1045 const bool nc_enabled = !source_is_if97;
1046 const double p_nc_lo = nc_enabled ? kNC_p_lo_mult * pc : 0.0;
1047 const double p_nc_hi = nc_enabled ? kNC_p_hi_mult * pc : 0.0;
1048 const double p_nc_sup_lo = nc_enabled ? kNC_sup_p_lo_mult * pc : 0.0;
1049 const double p_nc_sup_hi = nc_enabled ? kNC_sup_p_hi_mult * pc : 0.0;
1050 const double p_max = nc_enabled ? std::min(p_max_raw, p_nc_lo) : p_max_raw;
1051 const double p_min_sup = nc_enabled ? std::max(p_min_sup_raw, p_nc_sup_hi) : p_min_sup_raw;
1071 if (p_min < p_max) {
1075 spec.
regions.emplace_back(axis, std::move(t_lo), std::move(t_hi));
1078 if (p_min < p_max) {
1083 auto t_hi = std::make_unique<region::ConstantCurve>(p_min, p_max, T_max_eos - 0.5);
1084 spec.
regions.emplace_back(axis, std::move(t_lo), std::move(t_hi));
1090 if (nc_enabled && p_nc_lo < p_nc_hi) {
1095 spec.
regions.emplace_back(axis, std::move(t_lo), std::move(t_hi));
1100 auto t_hi = std::make_unique<region::ConstantCurve>(p_nc_lo, p_nc_hi, T_max_eos - 0.5);
1101 spec.
regions.emplace_back(axis, std::move(t_lo), std::move(t_hi));
1108 if (nc_enabled && p_nc_sup_lo < p_nc_sup_hi) {
1111 auto t_hi = std::make_unique<region::ConstantCurve>(p_nc_sup_lo, p_nc_sup_hi, T_max_eos - 0.5);
1112 spec.
regions.emplace_back(axis, std::move(t_lo), std::move(t_hi));
1118 if (p_min_sup < p_max_sup) {
1121 auto t_hi = std::make_unique<region::ConstantCurve>(p_min_sup, p_max_sup, T_max_eos - 0.5);
1122 spec.
regions.emplace_back(axis, std::move(t_lo), std::move(t_hi));
1130 if (source_is_if97) {
1141 const double kT_R5_lo = std::nextafter(1073.15, 2273.15);
1142 constexpr double kT_R5_hi = 2273.15;
1143 constexpr double kP_R5_hi = 50.0e6;
1146 constexpr double kP_R5_lo = 700.0;
1147 if (kP_R5_lo < kP_R5_hi) {
1148 const double p_r5_hi = std::min(p_max_sup, kP_R5_hi);
1150 auto t_lo = std::make_unique<region::ConstantCurve>(kP_R5_lo, p_r5_hi, kT_R5_lo);
1151 auto t_hi = std::make_unique<region::ConstantCurve>(kP_R5_lo, p_r5_hi, kT_R5_hi);
1152 spec.
regions.emplace_back(axis, std::move(t_lo), std::move(t_hi));
1175 throw std::invalid_argument(
"pt_subcritical: unsupported output property");
1188 std::vector<PropertySpec> ps = {
1227enum class WalkFloor : std::uint8_t
1233std::unique_ptr<region::CubicSplineCurve> build_rho_max_envelope(
::CoolProp::AbstractState& heos,
double T_min,
double T_max,
double p_target,
1234 WalkFloor floor_source, std::size_t n_knots = 64) {
1235 if (!(T_min > 0.0) || !(T_max > T_min) || n_knots < 2) {
1236 throw std::invalid_argument(
"build_rho_max_envelope: invalid T range or n_knots");
1238 std::vector<double> T_knots(n_knots);
1239 std::vector<double> y(n_knots);
1240 const double dT = (T_max - T_min) /
static_cast<double>(n_knots - 1);
1242 constexpr int kMaxHalvings = 50;
1243 for (std::size_t k = 0; k < n_knots; ++k) {
1244 T_knots[k] = T_min +
static_cast<double>(k) * dT;
1248 double p_floor = 0.0;
1249 if (floor_source == WalkFloor::SAT_L) {
1252 p_floor = heos.
p() * 1.001;
1253 }
catch (const ::CoolProp::CoolPropBaseError&) {
1258 p_floor = 1.001 * p_crit;
1266 double p_try = std::max(p_target, p_floor);
1267 double rho_found = std::nan(
"");
1268 double p_ok = std::nan(
"");
1269 double p_bad = std::nan(
"");
1270 for (
int i = 0; i < kMaxHalvings; ++i) {
1274 const double rho = heos.
rhomass();
1275 if (std::isfinite(rho)) {
1280 }
catch (const ::CoolProp::CoolPropBaseError&) {
1291 if (p_try <= p_floor)
break;
1292 p_try = std::max(0.5 * p_try, p_floor);
1297 if (std::isfinite(p_ok) && std::isfinite(p_bad) && p_bad > p_ok) {
1298 for (
int i = 0; i < kMaxHalvings; ++i) {
1299 if ((p_bad - p_ok) <= 1e-6 * p_bad)
break;
1300 const double p_mid = 0.5 * (p_ok + p_bad);
1304 const double rho = heos.
rhomass();
1305 if (std::isfinite(rho)) {
1310 }
catch (const ::CoolProp::CoolPropBaseError&) {
1314 if (!ok) p_bad = p_mid;
1317 if (!std::isfinite(rho_found)) {
1318 throw std::runtime_error(
"build_rho_max_envelope: no reachable p ∈ [" + std::to_string(p_floor) +
", " + std::to_string(p_target)
1319 +
"] at T=" + std::to_string(T_knots[k]));
1330std::unique_ptr<region::CubicSplineCurve> build_rho_min_envelope(
::CoolProp::AbstractState& heos,
double T_min,
double T_max,
double p_target,
1331 std::size_t n_knots = 64) {
1332 if (!(T_min > 0.0) || !(T_max > T_min) || n_knots < 2) {
1333 throw std::invalid_argument(
"build_rho_min_envelope: invalid T range or n_knots");
1335 std::vector<double> T_knots(n_knots);
1336 std::vector<double> y(n_knots);
1337 const double dT = (T_max - T_min) /
static_cast<double>(n_knots - 1);
1338 for (std::size_t k = 0; k < n_knots; ++k) {
1339 T_knots[k] = T_min +
static_cast<double>(k) * dT;
1343 }
catch (const ::CoolProp::CoolPropBaseError& e) {
1344 throw std::runtime_error(
"build_rho_min_envelope: HEOS rejected (T=" + std::to_string(T_knots[k]) +
", p=" + std::to_string(p_target)
1345 +
"): " + e.what());
1347 if (!std::isfinite(y[k])) {
1348 throw std::runtime_error(
"build_rho_min_envelope: non-finite rho at (T=" + std::to_string(T_knots[k]) +
", p=" + std::to_string(p_target)
1359 const std::size_t NT = opts.
NT;
1360 const std::size_t NR = opts.
NR;
1361 const std::int32_t rank = opts.
rank;
1375 const double T_triple = heos.
Ttriple();
1376 const double T_min_eos = std::max(T_triple, heos.
Tmin());
1377 const double T_max_eos = heos.
Tmax();
1381 const double T_sub_lo = std::max(T_min_eos, T_triple * 1.001);
1382 const double T_sub_hi = Tc * 0.99;
1384 const double T_sup_lo = Tc * 1.01;
1385 const double T_sup_hi = T_max_eos - 0.5;
1401 const double T_nc_sub_hi = Tc * (1.0 - 1.0e-6);
1402 const double T_nc_sup_lo = T_nc_sub_hi;
1429 const double p_min_eos = heos.
p() * 1.01;
1430 const double p_max_target = 0.99 * heos.
pmax();
1434 std::vector<double> T_breaks = {T_sub_lo};
1436 T_breaks.push_back(T_anom);
1438 T_breaks.push_back(T_sub_hi);
1454 for (std::size_t i = 0; i + 1 < T_breaks.size(); ++i) {
1455 const double T_lo = T_breaks[i];
1456 const double T_hi = T_breaks[i + 1];
1457 if (!(T_hi > T_lo))
continue;
1460 auto b_hi = build_rho_max_envelope(heos, T_lo, T_hi, p_max_target, WalkFloor::SAT_L);
1461 spec.
regions.emplace_back(axis, std::move(b_lo), std::move(b_hi));
1467 if (T_sub_hi > T_sub_lo) {
1469 auto b_lo = build_rho_min_envelope(heos, T_sub_lo, T_sub_hi, p_min_eos);
1483 if (T_sup_hi > T_sup_lo) {
1493 auto b_lo = build_rho_min_envelope(heos, T_sup_lo, T_sup_hi, p_min_eos);
1494 auto b_hi = build_rho_max_envelope(heos, T_sup_lo, T_sup_hi, p_max_target, WalkFloor::CRITICAL);
1514 if (T_nc_sub_hi > T_sub_hi) {
1517 auto b_hi = build_rho_max_envelope(heos, T_sub_hi, T_nc_sub_hi, p_max_target, WalkFloor::SAT_L);
1521 if (T_nc_sub_hi > T_sub_hi) {
1523 auto b_lo = build_rho_min_envelope(heos, T_sub_hi, T_nc_sub_hi, p_min_eos);
1536 if (T_sup_lo > T_nc_sup_lo) {
1538 auto b_lo = build_rho_min_envelope(heos, T_nc_sup_lo, T_sup_lo, p_min_eos);
1539 auto b_hi = build_rho_max_envelope(heos, T_nc_sup_lo, T_sup_lo, p_max_target, WalkFloor::CRITICAL);
1565 throw std::invalid_argument(
"dt_subcritical: unsupported output property");