40#include "../Backends/Helmholtz/HelmholtzEOSMixtureBackend.h"
43#if defined(ENABLE_CATCH)
44# include <catch2/catch_all.hpp>
68 SuperancGuard(
const SuperancGuard&) =
delete;
69 SuperancGuard& operator=(
const SuperancGuard&) =
delete;
70 SuperancGuard(SuperancGuard&&) =
delete;
71 SuperancGuard& operator=(SuperancGuard&&) =
delete;
74std::vector<double>
linspace(
double a,
double b, std::size_t n) {
75 std::vector<double> v(n);
76 for (std::size_t i = 0; i < n; ++i) {
77 v[i] = (n == 1) ? a : a + (b - a) *
static_cast<double>(i) /
static_cast<double>(n - 1);
113 INFO(
"pair=" << pair_name(pair) <<
" d_mass=" << d_mass <<
" other_in=" << other_in <<
" T_expect=" << T_expect);
115 rt.
update(pair, d_mass, other_in);
116 }
catch (
const std::exception& e) {
117 FAIL_CHECK(
"update threw: " << e.what());
120 FAIL_CHECK(
"update threw non-std exception");
123 const double d_out = rt.
rhomass();
124 const double other_out = other_mass(rt, pair);
125 CHECK(std::isfinite(d_out));
126 CHECK(std::isfinite(other_out));
127 CHECK(d_out == Catch::Approx(d_mass).epsilon(1e-5));
130 CHECK(other_out == Catch::Approx(other_in).epsilon(1e-5).margin(1e-3 * std::abs(other_in) + 1e-6));
138 CHECK(rt.
T() == Catch::Approx(T_expect).epsilon(1e-3));
150TEST_CASE(
"HSU_D: saturation-boundary round-trip (#2486)",
"[HSU_D][HSU_D_satbound][2486]") {
151 const std::string fluid = GENERATE(as<std::string>{},
"Water",
"n-Propane",
"R134a",
"CarbonDioxide",
"Nitrogen");
153 SuperancGuard guard(
true);
154 CAPTURE(fluid, pair_name(pair));
161 for (
double T :
linspace(Tt + 0.5, Tc - 0.5, 60)) {
162 for (
double Q : {0.0, 1.0}) {
165 const double d = ref->rhomass();
169 other = ref->hmass();
172 other = ref->smass();
175 other = ref->umass();
178 check_DX(*rt, pair, d, other,
T);
188TEST_CASE(
"HSU_D: two-phase interior round-trip (#2157,#1698,#1054)",
"[HSU_D][HSU_D_twophase][2157][1698][1054]") {
190 const std::string fluid = GENERATE(as<std::string>{},
"R1233zd(E)",
"R32",
"R134a",
"R245fa");
192 SuperancGuard guard(
true);
193 CAPTURE(fluid, pair_name(pair));
200 for (
double T :
linspace(Tt + 1.0, Tc - 1.0, 40)) {
201 for (
double Q : {0.01, 0.05, 0.1, 0.25, 0.5, 0.75, 0.9}) {
204 const double d = ref->rhomass();
206 check_DX(*rt, pair, d, other,
T);
223static const std::vector<double> kRhoFactors = {0.50, 0.75, 0.90, 0.95, 0.98, 0.99, 0.995, 0.999, 1.0,
224 1.001, 1.005, 1.01, 1.02, 1.05, 1.10, 1.25, 1.50, 2.00};
225static const std::vector<double> kTFactors = {0.90, 0.95, 0.98, 0.99, 0.995, 1.0, 1.005, 1.01, 1.02, 1.05, 1.10};
228TEST_CASE(
"HSU_D: near-critical (T,rho) box, all pairs (#2154,#2173)",
"[HSU_D][HSU_D_critdens][2154][2173]") {
229 const std::string fluid =
230 GENERATE(as<std::string>{},
"Water",
"CarbonDioxide",
"n-Propane",
"R134a",
"Nitrogen",
"Hydrogen",
"Helium",
"Methane");
232 SuperancGuard guard(
true);
233 CAPTURE(fluid, pair_name(pair));
240 for (
double tf : kTFactors) {
241 for (
double rf : kRhoFactors) {
245 if (tf == 1.0 && rf == 1.0)
continue;
246 const double T = tf * Tc, rho = rf * rhoc;
247 CAPTURE(tf, rf,
T, rho);
250 check_DX(*rt, pair, rho, other_mass(*ref, pair),
T);
258TEST_CASE(
"HSU_D: critical-isochore crossing (#2154,#1965)",
"[HSU_D][HSU_D_critdens][2154][1965]") {
259 const std::string fluid = GENERATE(as<std::string>{},
"Hydrogen",
"Helium",
"Nitrogen",
"CarbonDioxide",
"Water",
"Methane");
260 const double rf = GENERATE(0.97, 0.99, 1.0, 1.01, 1.03);
261 SuperancGuard guard(
true);
268 const double rho = rf * rhoc;
270 for (
double T :
linspace(0.92 * Tc, 1.12 * Tc, 40)) {
280TEST_CASE(
"HSU_D: near-critical two-phase dome edge (#2173)",
"[HSU_D][HSU_D_critdens][2173]") {
281 const std::string fluid = GENERATE(as<std::string>{},
"Hydrogen",
"Helium",
"CarbonDioxide",
"Water",
"n-Propane");
283 SuperancGuard guard(
true);
284 CAPTURE(fluid, pair_name(pair));
294 for (
double dT : {10.0, 1.0, 0.1, 0.01, 0.001}) {
295 const double T = Tc - dT;
296 if (
T <= Tt + 0.1)
continue;
297 for (
double Q : {0.1, 0.3, 0.5, 0.7, 0.9}) {
300 check_DX(*rt, pair, ref->rhomass(), other_mass(*ref, pair),
T);
306TEST_CASE(
"HSU_D: #2154 reported hydrogen D,U point",
"[HSU_D][2154]") {
307 SuperancGuard guard(
true);
309 REQUIRE_NOTHROW(
T =
CoolProp::PropsSI(
"T",
"D", 31.46258141,
"U", 2391760.261,
"Hydrogen"));
311 CHECK(std::isfinite(
T));
321TEST_CASE(
"HSU_D: #1965 nitrogen DmassUmass singular matrix",
"[HSU_D][1965]") {
322 SuperancGuard guard(
true);
330TEST_CASE(
"HSU_D: #1054 R245fa D,S isolated failure point",
"[HSU_D][1054]") {
331 SuperancGuard guard(
true);
334 for (
double d : {17.04, 17.05, 17.048396706125065}) {
335 for (
double s : {1.6174e3, 1.6175e3, 1617.463750828963}) {
349TEST_CASE(
"HSU_D-adjacent: #2022 HEOS::Air Hmass,Smass",
"[HSU_D][2022]") {
350 SuperancGuard guard(
true);
355 auto roundtrips = [&](
double h,
double s) ->
bool {
358 return std::isfinite(rt->
p()) && rt->
p() > 0 && rt->
hmass() == Catch::Approx(h).epsilon(1e-4)
359 && rt->
smass() == Catch::Approx(s).epsilon(1e-4);
360 }
catch (
const std::exception&) {
366 const std::vector<std::pair<double, double>> hs_ok = {
367 {429667.3064, 2735.612747}, {430867.4193, 2732.980272}, {427500.0, 2740.0}, {435000.0, 2724.0}};
368 for (
auto& [h, s] : hs_ok) {
370 CHECK(roundtrips(h, s));
381 const double h = 435000.0, s = 2654.0;
383 if (!roundtrips(h, s)) {
384 WARN(
"known-broken Air HmassSmass point (h=" << h <<
", s=" << s <<
") still diverges; see bd CoolProp-l34 / gh #2022");
393TEST_CASE(
"HSU_D: #2685 REFPROP::MM DmassUmass returns finite entropy",
"[HSU_D][2685][REFPROP]") {
400TEST_CASE(
"HSU_D: #2426 REFPROP::Hydrogen DmassUmass round-trip",
"[HSU_D][2426][REFPROP]") {
417TEST_CASE(
"HSU_D: water two-phase around the saturated-liquid density maximum",
"[HSU_D][HSU_D_wateranom]") {
419 SuperancGuard guard(
true);
420 CAPTURE(pair_name(pair));
426 double T_anom = 277.0, rhoL_max = 0.0;
427 for (
double T :
linspace(274.0, 295.0, 600)) {
429 if (ref->rhomass() > rhoL_max) {
430 rhoL_max = ref->rhomass();
434 CAPTURE(T_anom, rhoL_max);
438 for (
double T :
linspace(274.0, 300.0, 40)) {
439 for (
double Q : {0.0, 0.001, 0.01, 0.1, 0.5, 0.9, 1.0}) {
442 check_DX(*rt, pair, ref->rhomass(), other_mass(*ref, pair),
T);
448 for (
double p : {20e6, 50e6, 100e6}) {
449 for (
double T :
linspace(274.0, 320.0, 16)) {
452 check_DX(*rt, pair, ref->rhomass(), other_mass(*ref, pair),
T);
462TEST_CASE(
"HSU_D: two-phase EOS-polish config toggle",
"[HSU_D][HSU_D_polishcfg]") {
463 SuperancGuard sa(
true);
471 Restore(
const Restore&) =
delete;
472 Restore& operator=(
const Restore&) =
delete;
473 Restore(Restore&&) =
delete;
474 Restore& operator=(Restore&&) =
delete;
477 for (
const auto& fluid : {std::string(
"Water"), std::string(
"CarbonDioxide"), std::string(
"n-Propane")}) {
481 for (
double T :
linspace(Tt + 1.0, Tc - 1.0, 12)) {
482 for (
double Q : {0.05, 0.5, 0.95}) {
483 CAPTURE(fluid,
T, Q);
485 const double d = ref->rhomass(), s = ref->smass();
488 const double T_on = rt->
T();
491 const double T_off = rt->
T();
493 CHECK(T_on == Catch::Approx(
T).epsilon(1e-6));
494 CHECK(T_off == Catch::Approx(
T).epsilon(1e-5));
495 CHECK(std::abs(T_on - T_off) /
T < 1e-6);
514TEST_CASE(
"HSU_D: dense PT sweep over all superancillary fluids",
"[HSU_D_ptsweep][.]") {
515 SuperancGuard guard(
true);
518 const std::string fluid = GENERATE(from_range(fluids));
521 std::shared_ptr<CoolProp::AbstractState> ref, rt;
529 if (be ==
nullptr || !be->is_pure())
return;
530 std::shared_ptr<CoolProp::EquationOfState::SuperAncillary_t> sa;
538 const double Tmin = ref->Tmin(), Tmax = ref->Tmax(), pmax = ref->pmax();
541 plo = std::max(ref->p_triple(), pmax * 1e-8);
545 if (!(Tmax > Tmin) || !(pmax > plo))
return;
550 auto env_or = [](
const char* name, std::size_t def) -> std::size_t {
551 const char* v = std::getenv(name);
552 if (v ==
nullptr)
return def;
554 const long n = std::stol(v);
555 if (n > 1)
return static_cast<std::size_t
>(n);
561 const std::size_t NT = env_or(
"HSU_PTSWEEP_NT", 40), NP = env_or(
"HSU_PTSWEEP_NP", 30);
564 for (
double T :
linspace(Tmin + 0.1, Tmax, NT)) {
565 for (std::size_t j = 0; j < NP; ++j) {
568 const double p = plo * std::pow(pmax / plo,
static_cast<double>(j) /
static_cast<double>(NP - 1));
574 const double d = ref->rhomass();
575 if (!std::isfinite(d) || d <= 0)
continue;
576 for (
auto pr : pairs) {
585 check_DX(*rt, pr, d, other_mass(*ref, pr), -1);
607TEST_CASE(
"balance_matrix terminates on a non-finite companion entry",
"[HSU_D][balance_matrix]") {
608 Eigen::MatrixXd A(3, 3), Aprime, D;
613 A(0, 2) = -1.0 / (2.0 * std::numeric_limits<double>::min() * std::numeric_limits<double>::min());
614 REQUIRE(std::isinf(A(0, 2)));
620 CHECK(std::isfinite(D(1, 1)));
621 CHECK(D(1, 1) == 1.0);
632TEST_CASE(
"HSU_D: happy-vs-legacy speed",
"[HSU_D_bench][.]") {
636 AS.
update(ref_pair, a, b);
637 return std::pair<double, double>{AS.
rhomass(), other_mass(AS, out)};
643 BENCHMARK(
"Water two-phase D+S") {
651 BENCHMARK(
"Water two-phase D+U") {
659 BENCHMARK(
"Water liquid D+H") {
667 BENCHMARK(
"Water vapor D+U") {
675 BENCHMARK(
"CO2 supercritical D+H") {
683 BENCHMARK(
"CO2 near-crit two-phase D+S") {
695 const double d = co2->rhomass(), u = co2->umass();
696 BENCHMARK(
"CO2 near-crit density D+U") {
708TEST_CASE(
"HSU_D: two-phase call() component profile",
"[HSU_D_prof][.]") {
711 if (be ==
nullptr || !be->is_pure())
return;
712 std::shared_ptr<CoolProp::EquationOfState::SuperAncillary_t> sa;
719 const double T = 450.0;
720 const double rhoL = sa->eval_sat(
T,
'D', 0), rhoV = sa->eval_sat(
T,
'D', 1), p = sa->eval_sat(
T,
'P', 1);
722 BENCHMARK(
"3x eval_sat (Chebyshev only)") {
723 return sa->eval_sat(
T,
'D', 0) + sa->eval_sat(
T,
'D', 1) + sa->eval_sat(
T,
'P', 1);
725 BENCHMARK(
"1x update_TDmolarP_unchecked (no prop)") {
726 be->SatL->update_TDmolarP_unchecked(
T, rhoL, p);
727 return be->SatL->rhomolar();
729 BENCHMARK(
"1x keyed_output(Hmolar) after set") {
730 be->SatL->update_TDmolarP_unchecked(
T, rhoL, p);
733 BENCHMARK(
"FULL 2phase residual body (3 eval_sat + 2 set + 2 keyed_output)") {
734 const double rl = sa->eval_sat(
T,
'D', 0), rv = sa->eval_sat(
T,
'D', 1), pp = sa->eval_sat(
T,
'P', 1);
735 be->SatL->update_TDmolarP_unchecked(
T, rl, pp);
736 be->SatV->update_TDmolarP_unchecked(
T, rv, pp);
739 return yL + yV + rl + rv;
741 BENCHMARK(
"baseline: update_DmolarT_direct + hmolar (1 EOS eval)") {
742 be->update_DmolarT_direct(900.0,
T);
746 const double rho_2ph = 0.5 * (rhoL + rhoV);
747 BENCHMARK(
"get_all_intersections('D', rho)") {
748 return static_cast<double>(sa->get_all_intersections(
'D', rho_2ph, 48, 100, 1e-13).size());
750 BENCHMARK(
"commit: update(QT_INPUTS, Q, T)") {