CoolProp 8.0.0
An open-source fluid property and humid air property database
CoolProp-Tests-PXFlash.cpp
Go to the documentation of this file.
1// Focused reliability regression test for the P+{H,S,U} single-phase flash
2// (HSU_P_flash_singlephase_Brent in src/Backends/Helmholtz/FlashRoutines.cpp).
3//
4// Runs in the default suite (tag [PXflash], NOT [.]-hidden). Run explicitly:
5// ./CatchTestRunner "[PXflash]"
6//
7// Background (bd CoolProp-0nx): the solver left HEOS._p corrupted when the inner
8// PT density solve threw mid-Newton; the exception-fallback guard then branched
9// on the garbage pressure and re-threw instead of running the 2-D Newton
10// fallback. Two supercritical-cold (p > p_crit) Nitrogen/PS states failed.
11// The fix saves p_target at entry and lets the fallback engage for p > p_crit.
12//
13// Input-pair argument order (verified against include/DataStructures.h):
14// HmolarP_INPUTS : update(HmolarP_INPUTS, h_J_mol, p_Pa) (H first, P second)
15// PSmolar_INPUTS : update(PSmolar_INPUTS, p_Pa, s_J_mol_K) (P first, S second)
16// PUmolar_INPUTS : update(PUmolar_INPUTS, p_Pa, u_J_mol) (P first, U second)
17
18#if defined(ENABLE_CATCH)
19
20# include <catch2/catch_all.hpp>
21
22# include <cmath>
23# include <memory>
24# include <string>
25
28
29// ---------------------------------------------------------------------------
30// The two previously-failing Nitrogen states. Both are supercritical-cold
31// (p > p_crit ~ 3.396 MPa), the compressed-liquid corner. Before the fix the
32// P,S round-trip threw; the fix must make them solve and recover T and rho.
33// ---------------------------------------------------------------------------
34TEST_CASE("P+X supercritical-cold Nitrogen reliability (was failing)", "[PXflash]") {
35 struct State
36 {
37 double T; // K
38 double p; // Pa
39 };
40 const State pt = GENERATE(State{86.35, 4.754e6}, State{90.20, 7.768e6});
41 CAPTURE(pt.T, pt.p);
42
43 auto ref = std::shared_ptr<CoolProp::AbstractState>(CoolProp::AbstractState::factory("HEOS", "Nitrogen"));
44 auto wrk = std::shared_ptr<CoolProp::AbstractState>(CoolProp::AbstractState::factory("HEOS", "Nitrogen"));
45
46 // Establish the reference state at (T, p) and read its entropy.
47 REQUIRE_NOTHROW(ref->update(CoolProp::PT_INPUTS, pt.p, pt.T));
48 const double s = ref->smolar();
49 const double rho = ref->rhomolar();
50 REQUIRE(std::isfinite(s));
51 REQUIRE(std::isfinite(rho));
52
53 // The P,S flash that used to throw must now solve and round-trip.
54 REQUIRE_NOTHROW(wrk->update(CoolProp::PSmolar_INPUTS, pt.p, s));
55 CHECK(wrk->T() == Catch::Approx(pt.T).epsilon(1e-5));
56 CHECK(wrk->rhomolar() == Catch::Approx(rho).epsilon(1e-5));
57}
58
59// ---------------------------------------------------------------------------
60// Small, fast P+X round-trip guard across a few fluids/phases. Sets a known
61// (T, p) state, reads h/s/u, then runs each of the PH / PS / PU flashes and
62// confirms T and rho recover. Keeps CI cost low (a handful of points).
63// ---------------------------------------------------------------------------
64TEST_CASE("P+X single-phase round-trips (PH/PS/PU)", "[PXflash]") {
65 struct Case
66 {
67 const char* fluid;
68 double T; // K
69 double p; // Pa
70 const char* label;
71 };
72 const Case c = GENERATE(Case{"Water", 600.0, 1.0e5, "Water gas"}, // superheated vapor
73 Case{"Water", 320.0, 5.0e6, "Water liquid"}, // compressed liquid
74 Case{"CarbonDioxide", 320.0, 1.0e7, "CO2 supercrit"}, // p,T > critical
75 Case{"R134a", 250.0, 3.0e5, "R134a liquid"}, // subcooled liquid
76 Case{"R134a", 350.0, 2.0e5, "R134a gas"}); // superheated vapor
77 CAPTURE(c.label, c.fluid, c.T, c.p);
78
79 auto ref = std::shared_ptr<CoolProp::AbstractState>(CoolProp::AbstractState::factory("HEOS", c.fluid));
80 auto wrk = std::shared_ptr<CoolProp::AbstractState>(CoolProp::AbstractState::factory("HEOS", c.fluid));
81
82 REQUIRE_NOTHROW(ref->update(CoolProp::PT_INPUTS, c.p, c.T));
83 const double h = ref->hmolar();
84 const double s = ref->smolar();
85 const double u = ref->umolar();
86 const double rho = ref->rhomolar();
87 REQUIRE(std::isfinite(h));
88 REQUIRE(std::isfinite(s));
89 REQUIRE(std::isfinite(u));
90
91 SECTION("PH") {
92 REQUIRE_NOTHROW(wrk->update(CoolProp::HmolarP_INPUTS, h, c.p));
93 CHECK(wrk->T() == Catch::Approx(c.T).epsilon(1e-5));
94 CHECK(wrk->rhomolar() == Catch::Approx(rho).epsilon(1e-5));
95 }
96 SECTION("PS") {
97 REQUIRE_NOTHROW(wrk->update(CoolProp::PSmolar_INPUTS, c.p, s));
98 CHECK(wrk->T() == Catch::Approx(c.T).epsilon(1e-5));
99 CHECK(wrk->rhomolar() == Catch::Approx(rho).epsilon(1e-5));
100 }
101 SECTION("PU") {
102 REQUIRE_NOTHROW(wrk->update(CoolProp::PUmolar_INPUTS, c.p, u));
103 CHECK(wrk->T() == Catch::Approx(c.T).epsilon(1e-5));
104 CHECK(wrk->rhomolar() == Catch::Approx(rho).epsilon(1e-5));
105 }
106}
107
108// ---------------------------------------------------------------------------
109// High-pressure quantum-fluid P+X reliability (bd CoolProp-r1w7.1).
110//
111// At p >> psat_max (hundreds of MPa) with T just above Tc, the HSU_P flash's
112// T-search bracket straddles Tc. Without a melting line the search floor is
113// the bare EOS triple temperature, so trial T < Tc probe the cold solid-region
114// isotherm (a van der Waals loop pinned near p~0); solver_rho_Tp cannot find the
115// high-density root there and threw "Inputs in Brent [...] do not bracket the
116// root", aborting the flash before it could reach the valid T > Tc solution.
117// This was the largest exception bucket in the HEOS consistency report and is
118// perfectly correlated with a missing melting line (Hydrogen / ParaHydrogen,
119// which HAVE one, do not fail; OrthoHydrogen and the Deuterium isotopes, which
120// did not, failed ~684-702 times each). The fix gives these fluids a melting
121// line so the flash floors its T-search at Tmelt(p), above the loop. Points are
122// taken from the per-fluid consistency CSVs.
123// ---------------------------------------------------------------------------
124TEST_CASE("P+X high-pressure quantum-fluid reliability (was failing)", "[PXflash]") {
125 struct Case
126 {
127 const char* fluid;
128 double T; // K (> Tc, but the flash's T-search dips below Tc)
129 double p; // Pa (>> psat_max)
130 };
131 // Genuine fluid states (T > Tmelt(p)) at p >> psat_max that failed in the
132 // consistency report. Lower-T grid points at the same pressure are below the
133 // melting line (solid) and are correctly rejected, not solved. Adding the
134 // melting line (Datchi-2000 for H2; Diatschenko-1985 for the D2 isotopes)
135 // floors the HSU_P T-search at Tmelt(p) so these no longer fail.
136 const Case c =
137 GENERATE(Case{"OrthoHydrogen", 90.86892307692308, 404007839.5225975}, Case{"Deuterium", 79.32923076923078, 448875035.8934926},
138 Case{"OrthoDeuterium", 79.32923076923078, 448875035.8934926}, Case{"ParaDeuterium", 79.32923076923078, 448875035.8934926});
139 CAPTURE(c.fluid, c.T, c.p);
140
141 auto ref = std::shared_ptr<CoolProp::AbstractState>(CoolProp::AbstractState::factory("HEOS", c.fluid));
142 auto wrk = std::shared_ptr<CoolProp::AbstractState>(CoolProp::AbstractState::factory("HEOS", c.fluid));
143
144 REQUIRE_NOTHROW(ref->update(CoolProp::PT_INPUTS, c.p, c.T));
145 const double h = ref->hmolar();
146 const double s = ref->smolar();
147 const double u = ref->umolar();
148 const double rho = ref->rhomolar();
149 REQUIRE(std::isfinite(rho));
150
151 SECTION("PH") {
152 REQUIRE_NOTHROW(wrk->update(CoolProp::HmolarP_INPUTS, h, c.p));
153 CHECK(wrk->T() == Catch::Approx(c.T).epsilon(1e-5));
154 CHECK(wrk->rhomolar() == Catch::Approx(rho).epsilon(1e-5));
155 }
156 SECTION("PS") {
157 REQUIRE_NOTHROW(wrk->update(CoolProp::PSmolar_INPUTS, c.p, s));
158 CHECK(wrk->T() == Catch::Approx(c.T).epsilon(1e-5));
159 CHECK(wrk->rhomolar() == Catch::Approx(rho).epsilon(1e-5));
160 }
161 SECTION("PU") {
162 REQUIRE_NOTHROW(wrk->update(CoolProp::PUmolar_INPUTS, c.p, u));
163 CHECK(wrk->T() == Catch::Approx(c.T).epsilon(1e-5));
164 CHECK(wrk->rhomolar() == Catch::Approx(rho).epsilon(1e-5));
165 }
166}
167
168#endif // ENABLE_CATCH