5#include "../Backends/Helmholtz/HelmholtzEOSMixtureBackend.h"
6#include "../Backends/Helmholtz/HelmholtzEOSBackend.h"
7#include "../Backends/REFPROP/REFPROPMixtureBackend.h"
8#include "../Backends/Cubics/CubicBackend.h"
20#if defined(ENABLE_CATCH)
23# include <catch2/catch_all.hpp>
30namespace TransportValidation {
36 std::string in1, in2, out, fluid;
37 double v1, v2, tol, expected;
38 vel(std::string fluid, std::string in1,
double v1, std::string in2,
double v2,
const std::string& out,
double expected,
double tol)
39 : in1(in1), in2(in2), fluid(fluid), v1(v1), v2(v2), expected(expected), tol(tol) {
44vel viscosity_validation_data[] = {
46 vel(
"Propane",
"T", 90,
"Dmolar", 16.52e3,
"V", 7388e-6, 1e-3),
47 vel(
"Propane",
"T", 150,
"Dmolar", 15.14e3,
"V", 656.9e-6, 5e-3),
48 vel(
"Propane",
"T", 600,
"Dmolar", 10.03e3,
"V", 73.92e-6, 5e-3),
49 vel(
"Propane",
"T", 280,
"Dmolar", 11.78e3,
"V", 117.4e-6, 1e-3),
52 vel(
"n-Octane",
"T", 300,
"Dmolar", 6177.2,
"V", 553.60e-6, 1e-3),
53 vel(
"n-Nonane",
"T", 300,
"Dmolar", 5619.1,
"V", 709.53e-6, 1e-3),
54 vel(
"n-Decane",
"T", 300,
"Dmolar", 5150.4,
"V", 926.44e-6, 1e-3),
57 vel(
"n-Dodecane",
"T", 300,
"Dmolar", 4411.5,
"V", 1484.8e-6, 1e-3),
58 vel(
"n-Dodecane",
"T", 500,
"Dmolar", 3444.7,
"V", 183.76e-6, 1e-3),
61 vel(
"R125",
"T", 300,
"Dmolar", 10596.9998,
"V", 177.37e-6, 1e-3),
62 vel(
"R125",
"T", 400,
"Dmolar", 30.631,
"V", 17.070e-6, 1e-3),
65 vel(
"R134a",
"T", 185,
"Q", 0,
"V", 0.0012698376398294414, 1e-3),
66 vel(
"R134a",
"T", 185,
"Q", 1,
"V", 7.4290821400170869e-006, 1e-3),
67 vel(
"R134a",
"T", 360,
"Q", 0,
"V", 7.8146319978982133e-005, 1e-3),
68 vel(
"R134a",
"T", 360,
"Q", 1,
"V", 1.7140264998576107e-005, 1e-3),
71 vel(
"Ethanol",
"T", 300,
"Q", 0,
"V", 0.0010439017679191723, 1e-3),
72 vel(
"Ethanol",
"T", 300,
"Q", 1,
"V", 8.8293820936046416e-006, 1e-3),
73 vel(
"Ethanol",
"T", 500,
"Q", 0,
"V", 6.0979347125450671e-005, 1e-3),
74 vel(
"Ethanol",
"T", 500,
"Q", 1,
"V", 1.7229157141572511e-005, 1e-3),
78 vel(
"Hydrogen",
"T", 35,
"Dmass", 100,
"V", 5.47889e-005, 1e-3),
81 vel(
"DimethylEther",
"T", 253.146,
"Dmass", 734.28,
"V", 0.20444e-3, 3e-3),
82 vel(
"DimethylEther",
"T", 373.132,
"Dmass", 613.78,
"V", 0.09991e-3, 3e-3),
85 vel(
"Ammonia",
"T", 200,
"Dmolar", 3.9,
"V", 6.95e-6, 1e-3),
86 vel(
"Ammonia",
"T", 200,
"Dmolar", 42754.4,
"V", 507.28e-6, 1e-3),
87 vel(
"Ammonia",
"T", 398,
"Dmolar", 7044.7,
"V", 17.67e-6, 1e-3),
88 vel(
"Ammonia",
"T", 398,
"Dmolar", 21066.7,
"V", 43.95e-6, 1e-3),
91 vel(
"Nitrogen",
"T", 100,
"Dmolar", 1e-14,
"V", 6.90349e-6, 1e-3),
92 vel(
"Nitrogen",
"T", 300,
"Dmolar", 1e-14,
"V", 17.8771e-6, 1e-3),
93 vel(
"Nitrogen",
"T", 100,
"Dmolar", 25000,
"V", 79.7418e-6, 1e-3),
94 vel(
"Nitrogen",
"T", 200,
"Dmolar", 10000,
"V", 21.0810e-6, 1e-3),
95 vel(
"Nitrogen",
"T", 300,
"Dmolar", 5000,
"V", 20.7430e-6, 1e-3),
96 vel(
"Nitrogen",
"T", 126.195,
"Dmolar", 11180,
"V", 18.2978e-6, 1e-3),
97 vel(
"Argon",
"T", 100,
"Dmolar", 1e-14,
"V", 8.18940e-6, 1e-3),
98 vel(
"Argon",
"T", 300,
"Dmolar", 1e-14,
"V", 22.7241e-6, 1e-3),
99 vel(
"Argon",
"T", 100,
"Dmolar", 33000,
"V", 184.232e-6, 1e-3),
100 vel(
"Argon",
"T", 200,
"Dmolar", 10000,
"V", 25.5662e-6, 1e-3),
101 vel(
"Argon",
"T", 300,
"Dmolar", 5000,
"V", 26.3706e-6, 1e-3),
102 vel(
"Argon",
"T", 150.69,
"Dmolar", 13400,
"V", 27.6101e-6, 1e-3),
103 vel(
"Oxygen",
"T", 100,
"Dmolar", 1e-14,
"V", 7.70243e-6, 1e-3),
104 vel(
"Oxygen",
"T", 300,
"Dmolar", 1e-14,
"V", 20.6307e-6, 1e-3),
105 vel(
"Oxygen",
"T", 100,
"Dmolar", 35000,
"V", 172.136e-6, 1e-3),
106 vel(
"Oxygen",
"T", 200,
"Dmolar", 10000,
"V", 22.4445e-6, 1e-3),
107 vel(
"Oxygen",
"T", 300,
"Dmolar", 5000,
"V", 23.7577e-6, 1e-3),
108 vel(
"Oxygen",
"T", 154.6,
"Dmolar", 13600,
"V", 24.7898e-6, 1e-3),
109 vel(
"Air",
"T", 100,
"Dmolar", 1e-14,
"V", 7.09559e-6, 1e-3),
110 vel(
"Air",
"T", 300,
"Dmolar", 1e-14,
"V", 18.5230e-6, 1e-3),
111 vel(
"Air",
"T", 100,
"Dmolar", 28000,
"V", 107.923e-6, 1e-3),
112 vel(
"Air",
"T", 200,
"Dmolar", 10000,
"V", 21.1392e-6, 1e-3),
113 vel(
"Air",
"T", 300,
"Dmolar", 5000,
"V", 21.3241e-6, 1e-3),
114 vel(
"Air",
"T", 132.64,
"Dmolar", 10400,
"V", 17.7623e-6, 1e-3),
117 vel(
"Hexane",
"T", 250,
"Dmass", 1e-14,
"V", 5.2584e-6, 1e-3),
118 vel(
"Hexane",
"T", 400,
"Dmass", 1e-14,
"V", 8.4149e-6, 1e-3),
119 vel(
"Hexane",
"T", 550,
"Dmass", 1e-14,
"V", 11.442e-6, 1e-3),
120 vel(
"Hexane",
"T", 250,
"Dmass", 700,
"V", 528.2e-6, 1e-3),
121 vel(
"Hexane",
"T", 400,
"Dmass", 600,
"V", 177.62e-6, 1e-3),
122 vel(
"Hexane",
"T", 550,
"Dmass", 500,
"V", 95.002e-6, 1e-3),
125 vel(
"Heptane",
"T", 250,
"Dmass", 1e-14,
"V", 4.9717e-6, 1e-3),
126 vel(
"Heptane",
"T", 400,
"Dmass", 1e-14,
"V", 7.8361e-6, 1e-3),
127 vel(
"Heptane",
"T", 550,
"Dmass", 1e-14,
"V", 10.7394e-6, 1e-3),
128 vel(
"Heptane",
"T", 250,
"Dmass", 720,
"V", 725.69e-6, 1e-3),
129 vel(
"Heptane",
"T", 400,
"Dmass", 600,
"V", 175.94e-6, 1e-3),
130 vel(
"Heptane",
"T", 550,
"Dmass", 500,
"V", 95.105e-6, 1e-3),
133 vel(
"CO2",
"T", 100,
"Dmass", 1e-5,
"V", 0.0053757e-3, 1e-4),
134 vel(
"CO2",
"T", 2000,
"Dmass", 1e-5,
"V", 0.066079e-3, 1e-4),
135 vel(
"CO2",
"T", 10000,
"Dmass", 1e-5,
"V", 0.17620e-3, 1e-4),
136 vel(
"CO2",
"T", 220,
"Dmass", 3,
"V", 0.011104e-3, 1e-4),
137 vel(
"CO2",
"T", 225,
"Dmass", 1150,
"V", 0.22218e-3, 1e-4),
138 vel(
"CO2",
"T", 300,
"Dmass", 65,
"V", 0.015563e-3, 1e-4),
139 vel(
"CO2",
"T", 300,
"Dmass", 1400,
"V", 0.50594e-3, 1e-4),
140 vel(
"CO2",
"T", 700,
"Dmass", 100,
"V", 0.033112e-3, 1e-4),
141 vel(
"CO2",
"T", 700,
"Dmass", 1200,
"V", 0.22980e-3, 1e-4),
144 vel(
"R123",
"T", 265,
"Dmass", 1545.8,
"V", 627.1e-6, 1e-3),
145 vel(
"R123",
"T", 265,
"Dmass", 1.614,
"V", 9.534e-6, 1e-3),
146 vel(
"R123",
"T", 415,
"Dmass", 1079.4,
"V", 121.3e-6, 1e-3),
147 vel(
"R123",
"T", 415,
"Dmass", 118.9,
"V", 15.82e-6, 1e-3),
150 vel(
"Water",
"T", 298.15,
"Dmass", 998,
"V", 889.735100e-6, 1e-7),
151 vel(
"Water",
"T", 298.15,
"Dmass", 1200,
"V", 1437.649467e-6, 1e-7),
152 vel(
"Water",
"T", 373.15,
"Dmass", 1000,
"V", 307.883622e-6, 1e-7),
153 vel(
"Water",
"T", 433.15,
"Dmass", 1,
"V", 14.538324e-6, 1e-7),
154 vel(
"Water",
"T", 433.15,
"Dmass", 1000,
"V", 217.685358e-6, 1e-7),
155 vel(
"Water",
"T", 873.15,
"Dmass", 1,
"V", 32.619287e-6, 1e-7),
156 vel(
"Water",
"T", 873.15,
"Dmass", 100,
"V", 35.802262e-6, 1e-7),
157 vel(
"Water",
"T", 873.15,
"Dmass", 600,
"V", 77.430195e-6, 1e-7),
158 vel(
"Water",
"T", 1173.15,
"Dmass", 1,
"V", 44.217245e-6, 1e-7),
159 vel(
"Water",
"T", 1173.15,
"Dmass", 100,
"V", 47.640433e-6, 1e-7),
160 vel(
"Water",
"T", 1173.15,
"Dmass", 400,
"V", 64.154608e-6, 1e-7),
161 vel(
"Water",
"T", 647.35,
"Dmass", 122,
"V", 25.520677e-6, 1e-7),
162 vel(
"Water",
"T", 647.35,
"Dmass", 222,
"V", 31.337589e-6, 1e-7),
163 vel(
"Water",
"T", 647.35,
"Dmass", 272,
"V", 36.228143e-6, 1e-7),
164 vel(
"Water",
"T", 647.35,
"Dmass", 322,
"V", 42.961579e-6, 1e-7),
165 vel(
"Water",
"T", 647.35,
"Dmass", 372,
"V", 45.688204e-6, 1e-7),
166 vel(
"Water",
"T", 647.35,
"Dmass", 422,
"V", 49.436256e-6, 1e-7),
169 vel(
"SF6",
"T", 300,
"Dmass", 1e-14,
"V", 15.2887e-6, 1e-4),
170 vel(
"SF6",
"T", 300,
"Dmass", 5.92,
"V", 15.3043e-6, 1e-4),
171 vel(
"SF6",
"T", 300,
"Dmass", 1345.1,
"V", 117.417e-6, 1e-4),
172 vel(
"SF6",
"T", 400,
"Dmass", 1e-14,
"V", 19.6796e-6, 1e-4),
173 vel(
"SF6",
"T", 400,
"Dmass", 278.47,
"V", 24.4272e-6, 1e-4),
174 vel(
"SF6",
"T", 400,
"Dmass", 1123.8,
"V", 84.7835e-6, 1e-4),
177 vel(
"H2S",
"T", 200,
"P", 1000e5,
"V", 0.000460287, 1e-3),
178 vel(
"H2S",
"T", 200,
"P", 0.251702e5,
"V", 8.02322E-06, 1e-3),
179 vel(
"H2S",
"T", 596.961,
"P", 1000e5,
"V", 6.94741E-05, 1e-3),
180 vel(
"H2S",
"T", 596.961,
"P", 1e5,
"V", 2.38654E-05, 1e-3),
201 vel(
"Helium",
"T", 3.6,
"P", 0.180e6,
"V", 3.745e-6, 1e-2),
202 vel(
"Helium",
"T", 50,
"P", 0.180e6,
"V", 6.376e-6, 1e-2),
203 vel(
"Helium",
"T", 400,
"P", 0.180e6,
"V", 24.29e-6, 1e-2),
206 vel(
"R23",
"T", 180,
"Dmolar", 21097,
"V", 353.88e-6, 1e-4),
207 vel(
"R23",
"T", 420,
"Dmolar", 7564,
"V", 39.459e-6, 1e-4),
208 vel(
"R23",
"T", 370,
"Dmolar", 32.62,
"V", 18.213e-6, 1e-4),
211 vel(
"Ethane",
"T", 100,
"Dmolar", 21330,
"V", 878.6e-6, 1e-2),
212 vel(
"Ethane",
"T", 430,
"Dmolar", 12780,
"V", 58.70e-6, 1e-2),
213 vel(
"Ethane",
"T", 500,
"Dmolar", 11210,
"V", 48.34e-6, 1e-2),
216 vel(
"Methanol",
"T", 300,
"Dmass", 0.12955,
"V", 0.009696e-3, 1e-3),
217 vel(
"Methanol",
"T", 300,
"Dmass", 788.41,
"V", 0.5422e-3, 1e-3),
218 vel(
"Methanol",
"T", 630,
"Dmass", 0.061183,
"V", 0.02081e-3, 1e-3),
219 vel(
"Methanol",
"T", 630,
"Dmass", 888.50,
"V", 0.2405e-3, 1e-1),
222 vel(
"n-Butane",
"T", 150,
"Q", 0,
"V", 0.0013697657668, 1e-4),
223 vel(
"n-Butane",
"T", 400,
"Q", 1,
"V", 1.2027464524762453e-005, 1e-4),
224 vel(
"IsoButane",
"T", 120,
"Q", 0,
"V", 0.0060558450757844271, 1e-4),
225 vel(
"IsoButane",
"T", 400,
"Q", 1,
"V", 1.4761041187617117e-005, 2e-4),
226 vel(
"R134a",
"T", 175,
"Q", 0,
"V", 0.0017558494524138289, 1e-4),
227 vel(
"R134a",
"T", 360,
"Q", 1,
"V", 1.7140264998576107e-005, 1e-4),
230 vel(
"Cyclohexane",
"T", 300,
"Dmolar", 1e-10,
"V", 7.058e-6, 1e-4),
231 vel(
"Cyclohexane",
"T", 300,
"Dmolar", 0.0430e3,
"V", 6.977e-6, 1e-4),
232 vel(
"Cyclohexane",
"T", 300,
"Dmolar", 9.1756e3,
"V", 863.66e-6, 1e-4),
233 vel(
"Cyclohexane",
"T", 300,
"Dmolar", 9.9508e3,
"V", 2850.18e-6, 1e-4),
234 vel(
"Cyclohexane",
"T", 500,
"Dmolar", 1e-10,
"V", 11.189e-6, 1e-4),
235 vel(
"Cyclohexane",
"T", 500,
"Dmolar", 6.0213e3,
"V", 94.842e-6, 1e-4),
236 vel(
"Cyclohexane",
"T", 500,
"Dmolar", 8.5915e3,
"V", 380.04e-6, 1e-4),
237 vel(
"Cyclohexane",
"T", 700,
"Dmolar", 1e-10,
"V", 15.093e-6, 1e-4),
238 vel(
"Cyclohexane",
"T", 700,
"Dmolar", 7.4765e3,
"V", 176.749e-6, 1e-4),
241 vel(
"Benzene",
"T", 300,
"Dmass", 1e-10,
"V", 7.625e-6, 1e-4),
242 vel(
"Benzene",
"T", 400,
"Dmass", 1e-10,
"V", 10.102e-6, 1e-4),
243 vel(
"Benzene",
"T", 550,
"Dmass", 1e-10,
"V", 13.790e-6, 1e-4),
244 vel(
"Benzene",
"T", 300,
"Dmass", 875,
"V", 608.52e-6, 1e-4),
245 vel(
"Benzene",
"T", 400,
"Dmass", 760,
"V", 211.74e-6, 1e-4),
246 vel(
"Benzene",
"T", 550,
"Dmass", 500,
"V", 60.511e-6, 1e-4),
249 vel(
"m-Xylene",
"T", 300,
"Dmolar", 1e-10,
"V", 6.637e-6, 1e-4),
250 vel(
"m-Xylene",
"T", 300,
"Dmolar", 0.04 * 1e3,
"V", 6.564e-6, 1e-4),
251 vel(
"m-Xylene",
"T", 300,
"Dmolar", 8.0849 * 1e3,
"V", 569.680e-6, 1e-4),
252 vel(
"m-Xylene",
"T", 300,
"Dmolar", 8.9421 * 1e3,
"V", 1898.841e-6, 1e-4),
253 vel(
"m-Xylene",
"T", 400,
"Dmolar", 1e-10,
"V", 8.616e-6, 1e-4),
254 vel(
"m-Xylene",
"T", 400,
"Dmolar", 0.04 * 1e3,
"V", 8.585e-6, 1e-4),
255 vel(
"m-Xylene",
"T", 400,
"Dmolar", 7.2282 * 1e3,
"V", 238.785e-6, 1e-4),
256 vel(
"m-Xylene",
"T", 400,
"Dmolar", 8.4734 * 1e3,
"V", 718.950e-6, 1e-4),
257 vel(
"m-Xylene",
"T", 600,
"Dmolar", 1e-10,
"V", 12.841e-6, 1e-4),
258 vel(
"m-Xylene",
"T", 600,
"Dmolar", 0.04 * 1e3,
"V", 12.936e-6, 1e-4),
259 vel(
"m-Xylene",
"T", 600,
"Dmolar", 7.6591 * 1e3,
"V", 299.164e-6, 1e-4),
262 vel(
"o-Xylene",
"T", 300,
"Dmolar", 1e-10,
"V", 6.670e-6, 1e-4),
263 vel(
"o-Xylene",
"T", 300,
"Dmolar", 0.04 * 1e3,
"V", 6.598e-6, 1e-4),
264 vel(
"o-Xylene",
"T", 300,
"Dmolar", 8.2369 * 1e3,
"V", 738.286e-6, 1e-4),
265 vel(
"o-Xylene",
"T", 300,
"Dmolar", 8.7845 * 1e3,
"V", 1645.436e-6, 1e-4),
266 vel(
"o-Xylene",
"T", 400,
"Dmolar", 1e-10,
"V", 8.658e-6, 1e-4),
267 vel(
"o-Xylene",
"T", 400,
"Dmolar", 0.04 * 1e3,
"V", 8.634e-6, 1e-4),
268 vel(
"o-Xylene",
"T", 400,
"Dmolar", 7.4060 * 1e3,
"V", 279.954e-6, 1e-4),
269 vel(
"o-Xylene",
"T", 400,
"Dmolar", 8.2291 * 1e3,
"V", 595.652e-6, 1e-4),
270 vel(
"o-Xylene",
"T", 600,
"Dmolar", 1e-10,
"V", 12.904e-6, 1e-4),
271 vel(
"o-Xylene",
"T", 600,
"Dmolar", 0.04 * 1e3,
"V", 13.018e-6, 1e-4),
272 vel(
"o-Xylene",
"T", 600,
"Dmolar", 7.2408 * 1e3,
"V", 253.530e-6, 1e-4),
275 vel(
"p-Xylene",
"T", 300,
"Dmolar", 1e-10,
"V", 6.604e-6, 1e-4),
276 vel(
"p-Xylene",
"T", 300,
"Dmolar", 0.049 * 1e3,
"V", 6.405e-6, 1e-4),
277 vel(
"p-Xylene",
"T", 300,
"Dmolar", 8.0548 * 1e3,
"V", 593.272e-6, 1e-4),
278 vel(
"p-Xylene",
"T", 300,
"Dmolar", 8.6309 * 1e3,
"V", 1266.337e-6, 1e-4),
279 vel(
"p-Xylene",
"T", 400,
"Dmolar", 1e-10,
"V", 8.573e-6, 1e-4),
280 vel(
"p-Xylene",
"T", 400,
"Dmolar", 7.1995 * 1e3,
"V", 239.202e-6, 1e-4),
281 vel(
"p-Xylene",
"T", 400,
"Dmolar", 8.0735 * 1e3,
"V", 484.512e-6, 1e-4),
282 vel(
"p-Xylene",
"T", 600,
"Dmolar", 1e-10,
"V", 12.777e-6, 1e-4),
283 vel(
"p-Xylene",
"T", 600,
"Dmolar", 7.0985 * 1e3,
"V", 209.151e-6, 1e-4),
286 vel(
"EthylBenzene",
"T", 617,
"Dmass", 316,
"V", 33.22e-6, 1e-2),
289 vel(
"HeavyWater",
"T", 0.5000 * 643.847,
"Dmass", 3.07 * 358,
"V", 12.0604912273 * 55.2651e-6, 1e-5),
290 vel(
"HeavyWater",
"T", 0.9000 * 643.847,
"Dmass", 2.16 * 358,
"V", 1.6561616211 * 55.2651e-6, 1e-5),
291 vel(
"HeavyWater",
"T", 1.2000 * 643.847,
"Dmass", 0.8 * 358,
"V", 0.7651099154 * 55.2651e-6, 1e-5),
294 vel(
"Toluene",
"T", 300,
"Dmass", 1e-10,
"V", 7.023e-6, 1e-4),
295 vel(
"Toluene",
"T", 400,
"Dmass", 1e-10,
"V", 9.243e-6, 1e-4),
296 vel(
"Toluene",
"T", 550,
"Dmass", 1e-10,
"V", 12.607e-6, 1e-4),
297 vel(
"Toluene",
"T", 300,
"Dmass", 865,
"V", 566.78e-6, 1e-4),
298 vel(
"Toluene",
"T", 400,
"Dmass", 770,
"V", 232.75e-6, 1e-4),
299 vel(
"Toluene",
"T", 550,
"Dmass", 550,
"V", 80.267e-6, 1e-4),
303class TransportValidationFixture
307 shared_ptr<CoolProp::AbstractState> pState;
311 TransportValidationFixture() =
default;
312 ~TransportValidationFixture() =
default;
313 void set_backend(
const std::string& backend,
const std::string& fluid_name) {
316 void set_pair(std::string& in1,
double v1, std::string& in2,
double v2) {
321 pState->update(pair, o1, o2);
324 actual = pState->keyed_output(key);
328TEST_CASE_METHOD(TransportValidationFixture,
"Compare viscosities against published data",
"[viscosity],[transport]") {
329 int inputsN =
sizeof(viscosity_validation_data) /
sizeof(viscosity_validation_data[0]);
330 for (
int i = 0; i < inputsN; ++i) {
331 vel el = viscosity_validation_data[i];
332 CHECK_NOTHROW(set_backend(
"HEOS", el.fluid));
339 CHECK_NOTHROW(set_pair(el.in1, el.v1, el.in2, el.v2));
341 CAPTURE(el.expected);
343 CHECK(std::abs(actual / el.expected - 1) < el.tol);
347vel conductivity_validation_data[] = {
351 vel(
"Hexane",
"T", 250,
"Dmass", 700,
"L", 137.62e-3, 1e-4),
352 vel(
"Hexane",
"T", 400,
"Dmass", 2,
"L", 23.558e-3, 1e-4),
353 vel(
"Hexane",
"T", 400,
"Dmass", 650,
"L", 129.28e-3, 3e-4),
354 vel(
"Hexane",
"T", 510,
"Dmass", 2,
"L", 36.772e-3, 1e-4),
357 vel(
"Heptane",
"T", 250,
"Dmass", 720,
"L", 137.09e-3, 1e-4),
358 vel(
"Heptane",
"T", 400,
"Dmass", 2,
"L", 21.794e-3, 1e-4),
359 vel(
"Heptane",
"T", 400,
"Dmass", 650,
"L", 120.75e-3, 1e-4),
360 vel(
"Heptane",
"T", 535,
"Dmass", 100,
"L", 51.655e-3, 3e-3),
363 vel(
"Ethanol",
"T", 300,
"Dmass", 850,
"L", 209.68e-3, 1e-4),
364 vel(
"Ethanol",
"T", 400,
"Dmass", 2,
"L", 26.108e-3, 1e-4),
365 vel(
"Ethanol",
"T", 400,
"Dmass", 690,
"L", 149.21e-3, 1e-4),
366 vel(
"Ethanol",
"T", 500,
"Dmass", 10,
"L", 39.594e-3, 1e-4),
378 vel(
"SF6",
"T", 298.15,
"Dmass", 1e-13,
"L", 12.952e-3, 1e-4),
379 vel(
"SF6",
"T", 298.15,
"Dmass", 100,
"L", 14.126e-3, 1e-4),
380 vel(
"SF6",
"T", 298.15,
"Dmass", 1600,
"L", 69.729e-3, 1e-4),
381 vel(
"SF6",
"T", 310,
"Dmass", 1e-13,
"L", 13.834e-3, 1e-4),
382 vel(
"SF6",
"T", 310,
"Dmass", 1200,
"L", 48.705e-3, 1e-4),
383 vel(
"SF6",
"T", 480,
"Dmass", 100,
"L", 28.847e-3, 1e-4),
393 vel(
"Hydrogen",
"T", 298.15,
"Dmass", 1e-13,
"L", 185.67e-3, 1e-4),
394 vel(
"Hydrogen",
"T", 298.15,
"Dmass", 0.80844,
"L", 186.97e-3, 1e-4),
395 vel(
"Hydrogen",
"T", 298.15,
"Dmass", 14.4813,
"L", 201.35e-3, 1e-4),
396 vel(
"Hydrogen",
"T", 35,
"Dmass", 1e-13,
"L", 26.988e-3, 1e-4),
397 vel(
"Hydrogen",
"T", 35,
"Dmass", 30,
"L", 0.0770177, 1e-4),
398 vel(
"Hydrogen",
"T", 18,
"Dmass", 1e-13,
"L", 13.875e-3, 1e-4),
399 vel(
"Hydrogen",
"T", 18,
"Dmass", 75,
"L", 104.48e-3, 1e-4),
409 vel(
"R125",
"T", 341,
"Dmass", 600,
"L", 0.0565642978494, 2e-4),
410 vel(
"R125",
"T", 200,
"Dmass", 1e-13,
"L", 0.007036843623086, 2e-4),
411 vel(
"IsoButane",
"T", 390,
"Dmass", 387.09520158645068,
"L", 0.063039, 2e-4),
412 vel(
"IsoButane",
"T", 390,
"Dmass", 85.76703973869482,
"L", 0.036603, 2e-4),
413 vel(
"n-Butane",
"T", 415,
"Dmass", 360.01895129934866,
"L", 0.067045, 2e-4),
414 vel(
"n-Butane",
"T", 415,
"Dmass", 110.3113177144,
"L", 0.044449, 1e-4),
417 vel(
"n-Octane",
"T", 300,
"Dmolar", 6177.2,
"L", 0.12836, 1e-4),
418 vel(
"n-Nonane",
"T", 300,
"Dmolar", 5619.4,
"L", 0.13031, 1e-4),
422 vel(
"n-Dodecane",
"T", 300,
"Dmolar", 4411.5,
"L", 0.13829, 1e-4),
423 vel(
"n-Dodecane",
"T", 500,
"Dmolar", 3444.7,
"L", 0.09384, 1e-4),
424 vel(
"n-Dodecane",
"T", 660,
"Dmolar", 1500.98,
"L", 0.090346, 1e-4),
427 vel(
"n-Propane",
"T", 368,
"Q", 0,
"L", 0.07282154952457, 1e-3),
428 vel(
"n-Propane",
"T", 368,
"Dmolar", 1e-10,
"L", 0.0266135388745317, 1e-4),
441 vel(
"R123",
"T", 180,
"Dmass", 1739,
"L", 110.9e-3, 2e-4),
442 vel(
"R123",
"T", 180,
"Dmass", 0.2873e-2,
"L", 2.473e-3, 1e-3),
443 vel(
"R123",
"T", 430,
"Dmass", 996.35,
"L", 45.62e-3, 1e-3),
444 vel(
"R123",
"T", 430,
"Dmass", 166.9,
"L", 21.03e-3, 1e-3),
447 vel(
"CO2",
"T", 250.0,
"Dmass", 1e-6,
"L", 12.99e-3, 1e-3),
448 vel(
"CO2",
"T", 250.0,
"Dmass", 2.0,
"L", 13.05e-3, 1e-3),
449 vel(
"CO2",
"T", 250.0,
"Dmass", 1058.0,
"L", 140.00e-3, 1e-4),
450 vel(
"CO2",
"T", 310.0,
"Dmass", 400.0,
"L", 73.04e-3, 1e-4),
453 vel(
"Ethane",
"T", 100,
"Dmass", 1e-13,
"L", 3.46e-3, 1e-2),
454 vel(
"Ethane",
"T", 230,
"Dmolar", 16020,
"L", 126.2e-3, 1e-2),
455 vel(
"Ethane",
"T", 440,
"Dmolar", 1520,
"L", 45.9e-3, 1e-2),
456 vel(
"Ethane",
"T", 310,
"Dmolar", 4130,
"L", 45.4e-3, 1e-2),
459 vel(
"Nitrogen",
"T", 100,
"Dmolar", 1e-14,
"L", 9.27749e-3, 1e-4),
460 vel(
"Nitrogen",
"T", 300,
"Dmolar", 1e-14,
"L", 25.9361e-3, 1e-4),
461 vel(
"Nitrogen",
"T", 100,
"Dmolar", 25000,
"L", 103.834e-3, 1e-4),
462 vel(
"Nitrogen",
"T", 200,
"Dmolar", 10000,
"L", 36.0099e-3, 1e-4),
463 vel(
"Nitrogen",
"T", 300,
"Dmolar", 5000,
"L", 32.7694e-3, 1e-4),
464 vel(
"Nitrogen",
"T", 126.195,
"Dmolar", 11180,
"L", 675.800e-3, 1e-4),
465 vel(
"Argon",
"T", 100,
"Dmolar", 1e-14,
"L", 6.36587e-3, 1e-4),
466 vel(
"Argon",
"T", 300,
"Dmolar", 1e-14,
"L", 17.8042e-3, 1e-4),
467 vel(
"Argon",
"T", 100,
"Dmolar", 33000,
"L", 111.266e-3, 1e-4),
468 vel(
"Argon",
"T", 200,
"Dmolar", 10000,
"L", 26.1377e-3, 1e-4),
469 vel(
"Argon",
"T", 300,
"Dmolar", 5000,
"L", 23.2302e-3, 1e-4),
470 vel(
"Argon",
"T", 150.69,
"Dmolar", 13400,
"L", 856.793e-3, 1e-4),
471 vel(
"Oxygen",
"T", 100,
"Dmolar", 1e-14,
"L", 8.94334e-3, 1e-4),
472 vel(
"Oxygen",
"T", 300,
"Dmolar", 1e-14,
"L", 26.4403e-3, 1e-4),
473 vel(
"Oxygen",
"T", 100,
"Dmolar", 35000,
"L", 146.044e-3, 1e-4),
474 vel(
"Oxygen",
"T", 200,
"Dmolar", 10000,
"L", 34.6124e-3, 1e-4),
475 vel(
"Oxygen",
"T", 300,
"Dmolar", 5000,
"L", 32.5491e-3, 1e-4),
476 vel(
"Oxygen",
"T", 154.6,
"Dmolar", 13600,
"L", 377.476e-3, 1e-4),
477 vel(
"Air",
"T", 100,
"Dmolar", 1e-14,
"L", 9.35902e-3, 1e-4),
478 vel(
"Air",
"T", 300,
"Dmolar", 1e-14,
"L", 26.3529e-3, 1e-4),
479 vel(
"Air",
"T", 100,
"Dmolar", 28000,
"L", 119.221e-3, 1e-4),
480 vel(
"Air",
"T", 200,
"Dmolar", 10000,
"L", 35.3185e-3, 1e-4),
481 vel(
"Air",
"T", 300,
"Dmolar", 5000,
"L", 32.6062e-3, 1e-4),
482 vel(
"Air",
"T", 132.64,
"Dmolar", 10400,
"L", 75.6231e-3, 1e-4),
485 vel(
"Water",
"T", 298.15,
"Dmass", 1e-14,
"L", 18.4341883e-3, 1e-6),
486 vel(
"Water",
"T", 298.15,
"Dmass", 998,
"L", 607.712868e-3, 1e-6),
487 vel(
"Water",
"T", 298.15,
"Dmass", 1200,
"L", 799.038144e-3, 1e-6),
488 vel(
"Water",
"T", 873.15,
"Dmass", 1e-14,
"L", 79.1034659e-3, 1e-6),
489 vel(
"Water",
"T", 647.35,
"Dmass", 1,
"L", 51.9298924e-3, 1e-6),
490 vel(
"Water",
"T", 647.35,
"Dmass", 122,
"L", 130.922885e-3, 2e-4),
491 vel(
"Water",
"T", 647.35,
"Dmass", 222,
"L", 367.787459e-3, 2e-4),
492 vel(
"Water",
"T", 647.35,
"Dmass", 272,
"L", 757.959776e-3, 2e-4),
493 vel(
"Water",
"T", 647.35,
"Dmass", 322,
"L", 1443.75556e-3, 2e-4),
494 vel(
"Water",
"T", 647.35,
"Dmass", 372,
"L", 650.319402e-3, 2e-4),
495 vel(
"Water",
"T", 647.35,
"Dmass", 422,
"L", 448.883487e-3, 2e-4),
496 vel(
"Water",
"T", 647.35,
"Dmass", 750,
"L", 600.961346e-3, 2e-4),
499 vel(
"R23",
"T", 180,
"Dmolar", 21097,
"L", 143.19e-3, 1e-4),
500 vel(
"R23",
"T", 420,
"Dmolar", 7564,
"L", 50.19e-3, 2e-4),
501 vel(
"R23",
"T", 370,
"Dmolar", 32.62,
"L", 17.455e-3, 1e-4),
504 vel(
"Ammonia",
"T", 310,
"Dmolar", 34320,
"L", 0.45223303481784971, 1e-4),
505 vel(
"Ammonia",
"T", 395,
"Q", 0,
"L", 0.2264480769301, 2e-3),
508 vel(
"Helium",
"T", 800,
"P", 1e5,
"L", 0.3085, 1e-2),
509 vel(
"Helium",
"T", 300,
"P", 1e5,
"L", 0.1560, 1e-2),
510 vel(
"Helium",
"T", 20,
"P", 1e5,
"L", 0.0262, 1e-2),
511 vel(
"Helium",
"T", 8,
"P", 1e5,
"L", 0.0145, 1e-2),
512 vel(
"Helium",
"T", 4,
"P", 20e5,
"L", 0.0255, 1e-2),
513 vel(
"Helium",
"T", 8,
"P", 20e5,
"L", 0.0308, 1e-2),
514 vel(
"Helium",
"T", 20,
"P", 20e5,
"L", 0.0328, 1e-2),
515 vel(
"Helium",
"T", 4,
"P", 100e5,
"L", 0.0385, 3e-2),
516 vel(
"Helium",
"T", 8,
"P", 100e5,
"L", 0.0566, 3e-2),
517 vel(
"Helium",
"T", 20,
"P", 100e5,
"L", 0.0594, 1e-2),
518 vel(
"Helium",
"T", 4,
"P", 1e5,
"L", 0.0186, 1e-2),
519 vel(
"Helium",
"T", 4,
"P", 2e5,
"L", 0.0194, 1e-2),
520 vel(
"Helium",
"T", 5.180,
"P", 2.3e5,
"L", 0.0195, 1e-1),
521 vel(
"Helium",
"T", 5.2,
"P", 2.3e5,
"L", 0.0202, 1e-1),
522 vel(
"Helium",
"T", 5.230,
"P", 2.3e5,
"L", 0.0181, 1e-1),
523 vel(
"Helium",
"T", 5.260,
"P", 2.3e5,
"L", 0.0159, 1e-1),
524 vel(
"Helium",
"T", 5.3,
"P", 2.3e5,
"L", 0.0149, 1e-1),
537 vel(
"R134a",
"T", 240,
"D", 1e-10,
"L", 0.008698768, 1e-4),
538 vel(
"R134a",
"T", 330,
"D", 1e-10,
"L", 0.015907606, 1e-4),
539 vel(
"R134a",
"T", 330,
"Q", 0,
"L", 0.06746432253, 1e-4),
540 vel(
"R134a",
"T", 240,
"Q", 1,
"L", 0.00873242359, 1e-4),
543 vel(
"o-Xylene",
"T", 635,
"D", 270,
"L", 0.10387803232507065, 5e-3),
544 vel(
"m-Xylene",
"T", 616,
"D", 220,
"L", 0.10330950977360005, 5e-3),
545 vel(
"p-Xylene",
"T", 620,
"D", 287,
"L", 0.09804128875928533, 5e-3),
546 vel(
"EthylBenzene",
"T", 617,
"D", 316,
"L", 0.1479194493736235, 5e-2),
548 vel(
"o-Xylene",
"T", 300,
"D", 1e-12,
"L", 13.68e-3, 1e-3),
549 vel(
"o-Xylene",
"T", 600,
"D", 1e-12,
"L", 41.6e-3, 1e-3),
550 vel(
"m-Xylene",
"T", 300,
"D", 1e-12,
"L", 9.45e-3, 1e-3),
551 vel(
"m-Xylene",
"T", 600,
"D", 1e-12,
"L", 40.6e-3, 1e-3),
552 vel(
"p-Xylene",
"T", 300,
"D", 1e-12,
"L", 10.57e-3, 1e-3),
553 vel(
"p-Xylene",
"T", 600,
"D", 1e-12,
"L", 41.73e-3, 1e-3),
554 vel(
"EthylBenzene",
"T", 300,
"D", 1e-12,
"L", 9.71e-3, 1e-3),
555 vel(
"EthylBenzene",
"T", 600,
"D", 1e-12,
"L", 41.14e-3, 1e-3),
558 vel(
"Methane",
"T", 100,
"D", 1e-12,
"L", 9.83e-3, 1e-3),
559 vel(
"Methane",
"T", 400,
"D", 1e-12,
"L", 49.96e-3, 1e-3),
560 vel(
"Methane",
"T", 182,
"Q", 0,
"L", 82.5e-3, 5e-3),
561 vel(
"Methane",
"T", 100,
"Dmolar", 28.8e3,
"L", 234e-3, 1e-2),
564 vel(
"Methanol",
"T", 300,
"Dmass", 850,
"L", 241.48e-3, 1e-2),
565 vel(
"Methanol",
"T", 400,
"Dmass", 2,
"L", 25.803e-3, 1e-2),
566 vel(
"Methanol",
"T", 400,
"Dmass", 690,
"L", 183.59e-3, 1e-2),
567 vel(
"Methanol",
"T", 500,
"Dmass", 10,
"L", 40.495e-3, 1e-2),
570 vel(
"HeavyWater",
"T", 0.5000 * 643.847,
"Dmass", 3.07 * 358,
"V", 835.786416818 * 0.742128e-3, 1e-5),
571 vel(
"HeavyWater",
"T", 0.9000 * 643.847,
"Dmass", 2.16 * 358,
"V", 627.777590127 * 0.742128e-3, 1e-5),
572 vel(
"HeavyWater",
"T", 1.2000 * 643.847,
"Dmass", 0.8 * 358,
"V", 259.605241187 * 0.742128e-3, 1e-5),
575 vel(
"Cyclopentane",
"T", 512,
"Dmass", 1e-12,
"L", 37.042e-3, 1e-5),
576 vel(
"Cyclopentane",
"T", 512,
"Dmass", 400,
"L", 69.698e-3, 1e-1),
577 vel(
"Isopentane",
"T", 460,
"Dmass", 1e-12,
"L", 35.883e-3, 1e-4),
578 vel(
"Isopentane",
"T", 460,
"Dmass", 329.914,
"L", 59.649e-3, 1e-1),
579 vel(
"n-Pentane",
"T", 460,
"Dmass", 1e-12,
"L", 34.048e-3, 1e-5),
580 vel(
"n-Pentane",
"T", 460,
"Dmass", 377.687,
"L", 71.300e-3, 1e-1),
583TEST_CASE_METHOD(TransportValidationFixture,
"Compare thermal conductivities against published data",
"[conductivity],[transport]") {
584 int inputsN =
sizeof(conductivity_validation_data) /
sizeof(conductivity_validation_data[0]);
585 for (
int i = 0; i < inputsN; ++i) {
586 vel el = conductivity_validation_data[i];
587 CHECK_NOTHROW(set_backend(
"HEOS", el.fluid));
593 CHECK_NOTHROW(set_pair(el.in1, el.v1, el.in2, el.v2));
595 CAPTURE(el.expected);
597 CHECK(std::abs(actual / el.expected - 1) < el.tol);
625class ConsistencyFixture
628 CoolPropDbl hmolar, pmolar, smolar, umolar, rhomolar,
T, p, x1, x2;
629 shared_ptr<CoolProp::AbstractState> pState;
633 ConsistencyFixture() =
default;
634 ~ConsistencyFixture() =
default;
635 void set_backend(
const std::string& backend,
const std::string& fluid_name) {
655 void get_variables() {
721 void single_phase_consistency_check() {
723 State.
update(pair, x1, x2);
728 if (std::abs(p - State.
p()) / p * 100 > 1e-2)
731 void subcritical_pressure_liquid() {
733 int inputsN =
sizeof(inputs) /
sizeof(inputs[0]);
735 for (
double p = pState->p_triple() * 1.1; p < pState->p_critical(); p *= 3) {
736 double Ts =
PropsSI(
"T",
"P", p,
"Q", 0,
"Water");
741 for (
double T = Tmelt;
T < Ts - 0.1;
T += 0.1) {
742 CHECK_NOTHROW(set_TP(
T, p));
744 for (
int i = 0; i < inputsN; ++i) {
755 CHECK_NOTHROW(single_phase_consistency_check());
756 double rhomolar_RP =
PropsSI(
"Dmolar",
"P", p,
"T",
T,
"REFPROP::Water");
758 CAPTURE(rhomolar_RP);
760 CHECK(std::abs((rhomolar_RP - rhomolar) / rhomolar) < 1e-3);
768TEST_CASE_METHOD(ConsistencyFixture,
"Test all input pairs for Water using all valid backends",
"[consistency]") {
769 CHECK_NOTHROW(set_backend(
"HEOS",
"Water"));
770 subcritical_pressure_liquid();
796TEST_CASE(
"Test saturation properties for a few fluids",
"[saturation],[slow]") {
800 SECTION(
"All pressures are ok")
801 for (
double i : pv) {
808class HumidAirDewpointFixture
811 shared_ptr<CoolProp::AbstractState> AS;
812 std::vector<std::string> fluids;
813 std::vector<double> z;
814 void setup(
double zH2O) {
815 double z_Air[4] = {0.7810, 0.2095, 0.0092, 0.0003};
818 for (
int i = 0; i < 4; ++i) {
819 z[i + 1] = (1 - zH2O) * z_Air[i];
822 void run_p(
double p) {
827 constexpr std::size_t N_z = 999;
828 for (std::size_t i = 0; i < N_z; ++i) {
829 const double zH2O = 0.999 - 0.001 * i;
831 AS->set_mole_fractions(z);
833 CHECK_NOTHROW(AS->update(
PQ_INPUTS, p, 1));
834 if (AS->T() < 273.15) {
840 fluids =
strsplit(
"Water&Nitrogen&Oxygen&Argon&CO2",
'&');
841 AS.reset(AbstractState::factory(
"HEOS", fluids));
851TEST_CASE(
"HAPropsSI two-water-content inputs that uniquely determine dry-bulb temperature (issue #2670)",
"[humid_air][2670]") {
857 double T_dp = 283.15;
860 SECTION(
"T_dp + R gives dry-bulb temperature") {
864 CHECK(T_drybulb >= T_dp - 1e-6);
869 CHECK(std::abs(T_dp_check - T_dp) < 1e-4);
872 SECTION(
"W + R gives dry-bulb temperature") {
879 CHECK(T_drybulb >= T_dp - 1e-6);
884 CHECK(std::abs(R_check - R) < 1e-6);
887 SECTION(
"W + T_dp returns invalid (both fix psi_w, T is unconstrained)") {
906TEST_CASE(
"Humid-air virial-dependent properties are consistent with EOS virials",
"[humid_air][virial_cache]") {
911 const double P = 101325.0;
912 const double W = 0.01;
914 SECTION(
"compressibility Z is close to 1 at atmospheric conditions") {
915 for (
double T : {250.0, 273.15, 293.15, 333.15, 373.15}) {
919 CHECK(Z == Catch::Approx(1.0).margin(2e-3));
923 SECTION(
"HAPropsSI virial-path results reproduce across cache invalidation") {
929 CHECK(Z_T1_a == Z_T1_b);
932 SECTION(
"HAPropsSI enthalpy cache-invalidation reproduces") {
937 CHECK(H_T1_a == H_T1_b);
939 CHECK(H_T2 > H_T1_a);
954TEST_CASE(
"Humid-air h and s are consistent with individual EOS alpha0",
"[humid_air][alpha0_cache]") {
960 const double P = 101325.0;
961 const double Tvals[] = {213.15, 253.15, 293.15, 333.15, 373.15, 400.0};
962 const int NT =
static_cast<int>(
sizeof(Tvals) /
sizeof(Tvals[0]));
964 SECTION(
"dry air enthalpy monotonically increases with T") {
967 for (
int i = 1; i < NT; ++i) {
975 SECTION(
"dry air entropy monotonically increases with T") {
977 for (
int i = 1; i < NT; ++i) {
985 SECTION(
"h round-trip: T recovered from H at W=0") {
990 for (
double T : Tvals) {
995 CHECK(T_back == Catch::Approx(
T).epsilon(1e-6));
1018namespace HumidAirTests {
1020static double hap(
const char* out,
const char* k1,
double v1,
const char* k2,
double v2,
double p) {
1028TEST_CASE(
"ASHRAE RP-1485 A.6.1: Saturated air properties, T=-60..0 C, P=101.325 kPa",
"[humid_air_validation][ashrae_a61]") {
1032 using namespace HumidAirTests;
1033 const double P = 101325.0;
1036 for (
int i = 0; i <= 12; ++i) {
1037 const double T = (273.15 - 60.0) + i * 5.0;
1038 SECTION(std::string(
"T = ") + std::to_string(
static_cast<int>(
T - 273.15)) +
" C") {
1039 const double W = hap(
"W",
"T",
T,
"R", 1.0, P);
1040 const double h = hap(
"H",
"T",
T,
"R", 1.0, P);
1041 const double v = hap(
"V",
"T",
T,
"R", 1.0, P);
1042 const double s = hap(
"S",
"T",
T,
"R", 1.0, P);
1043 const double Twb = hap(
"Twb",
"T",
T,
"R", 1.0, P);
1044 const double Tdp = hap(
"D",
"T",
T,
"R", 1.0, P);
1058 CHECK(std::abs(Tdp -
T) < 1e-3);
1060 CHECK(std::abs(Twb -
T) < 1e-3);
1068TEST_CASE(
"ASHRAE RP-1485 A.6.2: Saturated air properties, T=0..90 C, P=101.325 kPa",
"[humid_air_validation][ashrae_a62]") {
1069 using namespace HumidAirTests;
1070 const double P = 101325.0;
1073 for (
int i = 0; i <= 18; ++i) {
1074 const double T = 273.15 + i * 5.0;
1075 SECTION(std::string(
"T = ") + std::to_string(
static_cast<int>(
T - 273.15)) +
" C") {
1076 const double W = hap(
"W",
"T",
T,
"R", 1.0, P);
1077 const double h = hap(
"H",
"T",
T,
"R", 1.0, P);
1078 const double v = hap(
"V",
"T",
T,
"R", 1.0, P);
1079 const double s = hap(
"S",
"T",
T,
"R", 1.0, P);
1080 const double Twb = hap(
"Twb",
"T",
T,
"R", 1.0, P);
1081 const double Tdp = hap(
"D",
"T",
T,
"R", 1.0, P);
1093 CHECK(std::abs(Tdp -
T) < 1e-3);
1095 CHECK(std::abs(Twb -
T) < 1e-3);
1103TEST_CASE(
"ASHRAE RP-1485 A.8: T=200 C, W=0..1 kg/kg, P=101 kPa..10 MPa",
"[humid_air_validation][ashrae_a8]") {
1104 using namespace HumidAirTests;
1105 const double T = 200.0 + 273.15;
1111 std::vector<double> Wvals;
1113 const PressureCase cases[] = {
1114 {101325.0, {0.0, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0}},
1115 {1000e3, {0.0, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0}},
1116 {2000e3, {0.0, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0}},
1117 {5000e3, {0.0, 0.05, 0.1, 0.15, 0.20, 0.25, 0.30}},
1118 {10000e3, {0.0, 0.05, 0.1}},
1121 for (
const auto& pc : cases) {
1122 for (
double W : pc.Wvals) {
1123 SECTION(
"P=" + std::to_string(
static_cast<int>(pc.p / 1000)) +
" kPa W=" + std::to_string(W)) {
1124 const double h = hap(
"H",
"T",
T,
"W", W, pc.p);
1125 const double v = hap(
"V",
"T",
T,
"W", W, pc.p);
1126 const double s = hap(
"S",
"T",
T,
"W", W, pc.p);
1127 const double R = hap(
"R",
"T",
T,
"W", W, pc.p);
1128 const double Twb = hap(
"Twb",
"T",
T,
"W", W, pc.p);
1139 CHECK(R <= 1.0 + 1e-9);
1144 CHECK(Twb <=
T + 1e-6);
1148 double W_check = hap(
"W",
"T",
T,
"R", R, pc.p);
1151 CHECK(std::abs(W_check - W) < W * 1e-6 + 1e-10);
1163TEST_CASE(
"ASHRAE RP-1485 A.9: T=320 C, W=0..1 kg/kg, P=101 kPa..10 MPa",
"[humid_air_validation][ashrae_a9]") {
1164 using namespace HumidAirTests;
1165 const double T = 320.0 + 273.15;
1170 std::vector<double> Wvals;
1172 const PressureCase cases[] = {
1173 {101325.0, {0.0, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0}},
1174 {1000e3, {0.0, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0}},
1175 {2000e3, {0.0, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0}},
1176 {5000e3, {0.0, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0}},
1177 {10000e3, {0.0, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0}},
1180 for (
const auto& pc : cases) {
1181 for (
double W : pc.Wvals) {
1182 SECTION(
"P=" + std::to_string(
static_cast<int>(pc.p / 1000)) +
" kPa W=" + std::to_string(W)) {
1183 const double h = hap(
"H",
"T",
T,
"W", W, pc.p);
1184 const double v = hap(
"V",
"T",
T,
"W", W, pc.p);
1185 const double s = hap(
"S",
"T",
T,
"W", W, pc.p);
1186 const double R = hap(
"R",
"T",
T,
"W", W, pc.p);
1187 const double Twb = hap(
"Twb",
"T",
T,
"W", W, pc.p);
1196 CHECK(R <= 1.0 + 1e-9);
1200 CHECK(Twb <=
T + 1e-6);
1203 double W_check = hap(
"W",
"T",
T,
"R", R, pc.p);
1206 CHECK(std::abs(W_check - W) < W * 1e-6 + 1e-10);
1218TEST_CASE(
"Humid air physical constraints: T_dp <= T_wb <= T_db, W >= 0, 0 <= R <= 1",
"[humid_air_validation][humid_air_physics]") {
1219 using namespace HumidAirTests;
1222 const double Tvals[] = {243.15, 263.15, 283.15, 293.15, 313.15, 333.15, 353.15};
1223 const double Rvals[] = {0.1, 0.3, 0.5, 0.7, 0.9};
1224 const double Pvals[] = {50000.0, 101325.0, 300000.0};
1226 for (
double T : Tvals) {
1227 for (
double R : Rvals) {
1228 for (
double p : Pvals) {
1229 SECTION(
"T=" + std::to_string(
static_cast<int>(
T - 273.15)) +
" R=" + std::to_string(
static_cast<int>(R * 100))
1230 +
"% P=" + std::to_string(
static_cast<int>(p))) {
1231 const double W = hap(
"W",
"T",
T,
"R", R, p);
1232 const double Tdp = hap(
"D",
"T",
T,
"R", R, p);
1233 const double Twb = hap(
"Twb",
"T",
T,
"R", R, p);
1234 const double R2 = hap(
"R",
"T",
T,
"W", W, p);
1242 CHECK(std::abs(R2 - R) < 1e-6);
1247 CHECK(Tdp <=
T + 1e-6);
1250 CHECK(Twb <=
T + 1e-6);
1251 CHECK(Twb >= 100.0);
1253 CHECK(Tdp <= Twb + 1e-4);
1265TEST_CASE(
"Humid air round-trip consistency: outputs used as inputs recover the original state",
"[humid_air_validation][humid_air_roundtrip]") {
1266 using namespace HumidAirTests;
1273 const Cond conds[] = {
1274 {253.15, 0.5, 101325.0},
1275 {273.15, 0.8, 101325.0},
1276 {293.15, 0.3, 101325.0},
1277 {293.15, 0.9, 101325.0},
1278 {313.15, 0.6, 101325.0},
1279 {293.15, 0.5, 200000.0},
1280 {293.15, 0.5, 50000.0},
1283 for (
const auto& c : conds) {
1284 SECTION(
"T=" + std::to_string(
static_cast<int>(c.T - 273.15)) +
" R=" + std::to_string(
static_cast<int>(c.R * 100))
1285 +
"% P=" + std::to_string(
static_cast<int>(c.p))) {
1287 const double W = hap(
"W",
"T", c.T,
"R", c.R, c.p);
1292 double R_check = hap(
"R",
"T", c.T,
"W", W, c.p);
1294 CHECK(std::abs(R_check - c.R) < 1e-6);
1299 double H = hap(
"H",
"T", c.T,
"R", c.R, c.p);
1301 double T_check = hap(
"T",
"H",
H,
"R", c.R, c.p);
1303 CHECK(std::abs(T_check - c.T) < 1e-3);
1308 double Tdp = hap(
"D",
"T", c.T,
"R", c.R, c.p);
1310 double W_check = hap(
"W",
"T", c.T,
"D", Tdp, c.p);
1312 CHECK(std::abs(W_check - W) < W * 1e-4 + 1e-12);
1319 double Twb = hap(
"Twb",
"T", c.T,
"R", c.R, c.p);
1322 double Twb_check = hap(
"Twb",
"T", c.T,
"W", W, c.p);
1325 CHECK(std::abs(Twb_check - Twb) < 1e-4);
1336TEST_CASE(
"Humid air auxiliary functions: physical validity and monotonicity",
"[humid_air_validation][humid_air_aux]") {
1340 SECTION(
"Enhancement factor f >= 1.0 for all T and P") {
1343 const double Tvals[] = {213.15, 253.15, 273.15, 293.15, 313.15, 353.15, 423.15, 623.15};
1344 const double Pvals[] = {101325.0, 200000.0, 500000.0, 1000000.0, 10000000.0};
1345 for (
double T : Tvals) {
1346 for (
double p : Pvals) {
1351 CHECK(f >= 1.0 - 1e-9);
1356 SECTION(
"Enhancement factor increases with pressure at fixed T") {
1358 const double T = 293.15;
1359 const double Pvals[] = {101325.0, 200000.0, 500000.0, 1000000.0, 5000000.0};
1360 double f_prev = 0.0;
1361 for (
double p : Pvals) {
1365 CHECK(f >= f_prev - 1e-9);
1370 SECTION(
"Saturation pressure p_ws is positive and increases with T") {
1371 const double Tvals[] = {213.15, 233.15, 253.15, 273.15, 293.15, 313.15, 333.15, 353.15};
1372 double p_ws_prev = 0.0;
1373 for (
double T : Tvals) {
1379 CHECK(p_ws > p_ws_prev);
1384 SECTION(
"Henry constant is positive for liquid water; not finite below ice point") {
1387 const double T_ice = 263.15;
1388 const double T_liq1 = 283.15;
1389 const double T_liq2 = 313.15;
1394 CHECK(bH_liq1 > 0.0);
1395 CHECK(bH_liq2 > 0.0);
1398 SECTION(
"Isothermal compressibility kT is positive for liquid water") {
1399 const double Tvals[] = {283.15, 303.15, 323.15, 353.15};
1400 const double Pvals[] = {101325.0, 500000.0, 1000000.0};
1401 for (
double T : Tvals) {
1402 for (
double p : Pvals) {
1412 SECTION(
"Saturated molar volume vbar_ws is physically sane") {
1418 const double Tvals[] = {213.15, 253.15, 273.15, 293.15, 333.15, 373.15};
1419 const double Pvals[] = {101325.0, 500000.0, 1000000.0};
1420 for (
double T : Tvals) {
1421 for (
double p : Pvals) {
1432 SECTION(
"vbar_ws is continuous across the ice/liquid boundary (GH #2657)") {
1436 const double p = 101325.0;
1443 CHECK(v_ice > 0.9 * v_liq);
1444 CHECK(v_ice < 1.2 * v_liq);
1447 SECTION(
"Virial coefficients Baa and Bww have expected signs") {
1449 const double T = 293.15;
1458 SECTION(
"Cross virial coefficient Baw") {
1459 const double T = 293.15;
1466TEST_CASE(
"Test consistency between Gernert models in CoolProp and Gernert models in REFPROP",
"[Gernert]") {
1470 std::string mixes[] = {
"CO2[0.7]&Argon[0.3]",
"CO2[0.7]&Water[0.3]",
"CO2[0.7]&Nitrogen[0.3]"};
1471 for (
const auto& mix : mixes) {
1472 const char* ykey = mix.c_str();
1473 std::ostringstream ss1;
1475 SECTION(ss1.str(),
"") {
1476 double Tnbp_CP, Tnbp_RP, R_RP, R_CP, pchk_CP, pchk_RP;
1477 CHECK_NOTHROW(R_CP =
PropsSI(
"gas_constant",
"P", 101325,
"Q", 1,
"HEOS::" + mix));
1479 CHECK_NOTHROW(R_RP =
PropsSI(
"gas_constant",
"P", 101325,
"Q", 1,
"REFPROP::" + mix));
1481 CHECK_NOTHROW(Tnbp_CP =
PropsSI(
"T",
"P", 101325,
"Q", 1,
"HEOS::" + mix));
1483 CHECK_NOTHROW(pchk_CP =
PropsSI(
"P",
"T", Tnbp_CP,
"Q", 1,
"HEOS::" + mix));
1485 CHECK_NOTHROW(Tnbp_RP =
PropsSI(
"T",
"P", 101325,
"Q", 1,
"REFPROP::" + mix));
1487 CHECK_NOTHROW(pchk_RP =
PropsSI(
"P",
"T", Tnbp_RP,
"Q", 1,
"REFPROP::" + mix));
1489 double diff = std::abs(Tnbp_CP / Tnbp_RP - 1);
1495TEST_CASE(
"Tests for solvers in P,T flash using Water",
"[flash],[PT]") {
1496 SECTION(
"Check that T,P for saturated state yields error") {
1498 CHECK_NOTHROW(Ts =
PropsSI(
"T",
"P", 101325,
"Q", 0,
"Water"));
1500 CHECK_NOTHROW(ps =
PropsSI(
"P",
"T", Ts,
"Q", 0,
"Water"));
1504 CHECK_NOTHROW(rho =
PropsSI(
"D",
"T", Ts,
"P", ps,
"Water"));
1508 SECTION(
"Subcritical p slightly subcooled should be ok") {
1509 double Ts, rho, dT = 1e-4;
1510 CHECK_NOTHROW(Ts =
PropsSI(
"T",
"P", 101325,
"Q", 0,
"Water"));
1514 CHECK_NOTHROW(rho =
PropsSI(
"D",
"T", Ts - dT,
"P", 101325,
"Water"));
1518 SECTION(
"Subcritical p slightly superheated should be ok") {
1519 double Ts, rho, dT = 1e-4;
1520 CHECK_NOTHROW(Ts =
PropsSI(
"T",
"P", 101325,
"Q", 0,
"Water"));
1524 CHECK_NOTHROW(rho =
PropsSI(
"D",
"T", Ts + dT,
"P", 101325,
"Water"));
1530TEST_CASE(
"P,T flash at the critical point returns rhomolar_critical",
"[flash],[PT],[critical_point],[2738]") {
1533 for (
const std::string fluid : {
"CarbonDioxide",
"Water",
"R134a"}) {
1535 std::shared_ptr<AbstractState> AS(AbstractState::factory(
"HEOS", fluid));
1536 double Tc = AS->T_critical();
1537 double pc = AS->p_critical();
1538 double rho_c = AS->rhomolar_critical();
1540 CHECK(std::abs(AS->rhomolar() - rho_c) / rho_c < 1e-10);
1543 SECTION(
"Issue #2738 reproducer (high-level API for CO2)") {
1544 double Tc =
Props1SI(
"CO2",
"Tcrit");
1545 double pc =
Props1SI(
"CO2",
"Pcrit");
1546 double rho_crit =
Props1SI(
"CO2",
"rhomass_critical");
1547 double rho_pt =
PropsSI(
"Dmass",
"T", Tc,
"P", pc,
"CO2");
1553 CHECK(std::abs(rho_pt - rho_crit) / rho_crit < 1e-10);
1557TEST_CASE(
"Tests for solvers in P,Y flash using Water",
"[flash],[PH],[PS],[PU]") {
1560 const std::vector<std::string> Ykeys = {
"H",
"S",
"U",
"Hmass",
"Smass",
"Umass",
"Hmolar",
"Smolar",
"Umolar"};
1561 for (
const auto& Ykey : Ykeys) {
1562 const char* ykey = Ykey.c_str();
1563 std::ostringstream ss1;
1564 ss1 <<
"Subcritical superheated P," << ykey;
1565 SECTION(ss1.str(),
"") {
1567 CHECK_NOTHROW(Ts =
PropsSI(
"T",
"P", 101325,
"Q", 0,
"Water"));
1570 CHECK_NOTHROW(y =
PropsSI(ykey,
"T", Ts + dT,
"P", 101325,
"Water"));
1574 CHECK_NOTHROW(T2 =
PropsSI(
"T", ykey, y,
"P", 101325,
"Water"));
1579 std::ostringstream ss2;
1580 ss2 <<
"Subcritical barely superheated P," << ykey;
1581 SECTION(ss2.str(),
"") {
1583 CHECK_NOTHROW(Ts =
PropsSI(
"T",
"P", 101325,
"Q", 0,
"Water"));
1586 CHECK_NOTHROW(y =
PropsSI(ykey,
"T", Ts + dT,
"P", 101325,
"Water"));
1590 CHECK_NOTHROW(T2 =
PropsSI(
"T", ykey, y,
"P", 101325,
"Water"));
1595 std::ostringstream ss3;
1596 ss3 <<
"Subcritical subcooled P," << ykey;
1597 SECTION(ss3.str(),
"") {
1599 CHECK_NOTHROW(Ts =
PropsSI(
"T",
"P", 101325,
"Q", 0,
"Water"));
1602 CHECK_NOTHROW(y =
PropsSI(ykey,
"T", Ts + dT,
"P", 101325,
"Water"));
1606 CHECK_NOTHROW(T2 =
PropsSI(
"T", ykey, y,
"P", 101325,
"Water"));
1611 std::ostringstream ss4;
1612 ss4 <<
"Subcritical barely subcooled P," << ykey;
1613 SECTION(ss4.str(),
"") {
1615 CHECK_NOTHROW(Ts =
PropsSI(
"T",
"P", 101325,
"Q", 0,
"Water"));
1618 CHECK_NOTHROW(y =
PropsSI(ykey,
"T", Ts + dT,
"P", 101325,
"Water"));
1622 CHECK_NOTHROW(T2 =
PropsSI(
"T", ykey, y,
"P", 101325,
"Water"));
1627 std::ostringstream ss5;
1628 ss5 <<
"Supercritical P," << ykey;
1629 SECTION(ss5.str(),
"") {
1630 double Tc =
Props1SI(
"Water",
"Tcrit");
1631 double pc =
Props1SI(
"Water",
"pcrit");
1632 double p = pc * 1.3;
1633 double T = Tc * 1.3;
1638 CHECK_NOTHROW(y =
PropsSI(ykey,
"P", p,
"T",
T,
"Water"));
1641 CHECK_NOTHROW(T2 =
PropsSI(
"T", ykey, y,
"P", p,
"Water"));
1646 std::ostringstream ss6;
1647 ss6 <<
"Supercritical \"gas\" P," << ykey;
1648 SECTION(ss6.str(),
"") {
1649 double Tc =
Props1SI(
"Water",
"Tcrit");
1650 double pc =
Props1SI(
"Water",
"pcrit");
1651 double p = pc * 0.7;
1652 double T = Tc * 1.3;
1657 CHECK_NOTHROW(y =
PropsSI(ykey,
"P", p,
"T",
T,
"Water"));
1660 CHECK_NOTHROW(T2 =
PropsSI(
"T", ykey, y,
"P", p,
"Water"));
1665 std::ostringstream ss7;
1666 ss7 <<
"Supercritical \"liquid\" P," << ykey;
1667 SECTION(ss7.str(),
"") {
1668 double Tc =
Props1SI(
"Water",
"Tcrit");
1669 double pc =
Props1SI(
"Water",
"pcrit");
1671 double T = Tc * 0.5;
1676 CHECK_NOTHROW(y =
PropsSI(ykey,
"P", p,
"T",
T,
"Water"));
1679 CHECK_NOTHROW(T2 =
PropsSI(
"T", ykey, y,
"P", p,
"Water"));
1687TEST_CASE(
"R134A saturation bug in dev",
"[2545]") {
1691 CHECK(p == Catch::Approx(291215));
1694TEST_CASE(
"Tests for solvers in P,H flash using Propane",
"[flashdups],[flash],[PH],[consistency]") {
1695 double hmolar, hmass;
1696 SECTION(
"5 times PH with HEOS AbstractState yields same results every time",
"") {
1700 hmolar = AS->hmolar();
1701 hmass = AS->hmass();
1704 hmolar = AS->hmolar();
1705 hmass = AS->hmass();
1708 hmolar = AS->hmolar();
1709 hmass = AS->hmass();
1712 hmolar = AS->hmolar();
1713 hmass = AS->hmass();
1716 hmolar = AS->hmolar();
1717 hmass = AS->hmass();
1723TEST_CASE(
"Multiple calls to state class are consistent",
"[flashdups],[flash],[PH],[consistency]") {
1724 double hmolar, hmass;
1725 SECTION(
"3 times PH with HEOS AbstractState yields same results every time",
"") {
1729 hmolar = AS->hmolar();
1730 hmass = AS->hmass();
1733 hmolar = AS->hmolar();
1734 hmass = AS->hmass();
1737 hmolar = AS->hmolar();
1738 hmass = AS->hmass();
1744TEST_CASE(
"Test first partial derivatives using PropsSI",
"[derivatives]") {
1746 SECTION(
"Check drhodp|T 3 ways",
"") {
1751 double drhomolardp__T_PropsSI_num =
1752 (
PropsSI(
"Dmolar",
"T",
T,
"P", 101325 + 1e-3,
"n-Propane") -
PropsSI(
"Dmolar",
"T",
T,
"P", 101325 - 1e-3,
"n-Propane")) / (2 * 1e-3);
1753 double drhomolardp__T_PropsSI =
PropsSI(
"d(Dmolar)/d(P)|T",
"T",
T,
"P", 101325,
"n-Propane");
1755 CAPTURE(drhomolardp__T_AbstractState);
1756 CAPTURE(drhomolardp__T_PropsSI_num);
1757 CAPTURE(drhomolardp__T_PropsSI);
1758 double rel_err_exact = std::abs((drhomolardp__T_AbstractState - drhomolardp__T_PropsSI) / drhomolardp__T_PropsSI);
1759 double rel_err_approx = std::abs((drhomolardp__T_PropsSI_num - drhomolardp__T_PropsSI) / drhomolardp__T_PropsSI);
1760 CHECK(rel_err_exact < 1e-7);
1761 CHECK(rel_err_approx < 1e-7);
1763 SECTION(
"Check drhodp|T 3 ways for water",
"") {
1769 double drhomolardp__T_PropsSI_num =
1770 (
PropsSI(
"Dmolar",
"T",
T,
"P", 101325 + 1,
"Water") -
PropsSI(
"Dmolar",
"T",
T,
"P", 101325 - 1,
"Water")) / (2 * 1);
1771 double drhomolardp__T_PropsSI =
PropsSI(
"d(Dmolar)/d(P)|T",
"T",
T,
"P", 101325,
"Water");
1773 CAPTURE(drhomolardp__T_AbstractState);
1774 CAPTURE(drhomolardp__T_PropsSI_num);
1775 CAPTURE(drhomolardp__T_PropsSI);
1776 double rel_err_exact = std::abs((drhomolardp__T_AbstractState - drhomolardp__T_PropsSI) / drhomolardp__T_PropsSI);
1777 double rel_err_approx = std::abs((drhomolardp__T_PropsSI_num - drhomolardp__T_PropsSI) / drhomolardp__T_PropsSI);
1778 CHECK(rel_err_exact < 1e-4);
1779 CHECK(rel_err_approx < 1e-4);
1781 SECTION(
"Check dpdrho|T 3 ways for water",
"") {
1787 double dpdrhomolar__T_PropsSI_num =
1788 (
PropsSI(
"P",
"T",
T,
"Dmolar", rhomolar + 1e-3,
"Water") -
PropsSI(
"P",
"T",
T,
"Dmolar", rhomolar - 1e-3,
"Water")) / (2 * 1e-3);
1789 double dpdrhomolar__T_PropsSI =
PropsSI(
"d(P)/d(Dmolar)|T",
"T",
T,
"P", 101325,
"Water");
1791 CAPTURE(dpdrhomolar__T_AbstractState);
1792 CAPTURE(dpdrhomolar__T_PropsSI_num);
1793 CAPTURE(dpdrhomolar__T_PropsSI);
1794 double rel_err_exact = std::abs((dpdrhomolar__T_AbstractState - dpdrhomolar__T_PropsSI) / dpdrhomolar__T_PropsSI);
1795 double rel_err_approx = std::abs((dpdrhomolar__T_PropsSI_num - dpdrhomolar__T_PropsSI) / dpdrhomolar__T_PropsSI);
1796 CHECK(rel_err_exact < 1e-6);
1797 CHECK(rel_err_approx < 1e-6);
1799 SECTION(
"Check dpdrho|T 3 ways for water using mass based",
"") {
1805 double dpdrhomass__T_PropsSI_num =
1806 (
PropsSI(
"P",
"T",
T,
"Dmass", rhomass + 1e-3,
"Water") -
PropsSI(
"P",
"T",
T,
"Dmass", rhomass - 1e-3,
"Water")) / (2 * 1e-3);
1807 double dpdrhomass__T_PropsSI =
PropsSI(
"d(P)/d(Dmass)|T",
"T",
T,
"P", 101325,
"Water");
1809 CAPTURE(dpdrhomass__T_AbstractState);
1810 CAPTURE(dpdrhomass__T_PropsSI_num);
1811 CAPTURE(dpdrhomass__T_PropsSI);
1812 double rel_err_exact = std::abs((dpdrhomass__T_AbstractState - dpdrhomass__T_PropsSI) / dpdrhomass__T_PropsSI);
1813 double rel_err_approx = std::abs((dpdrhomass__T_PropsSI_num - dpdrhomass__T_PropsSI) / dpdrhomass__T_PropsSI);
1814 CHECK(rel_err_exact < 1e-7);
1815 CHECK(rel_err_approx < 1e-7);
1817 SECTION(
"Invalid first partial derivatives",
"") {
1824 CHECK(!
ValidNumber(
PropsSI(
"d(Bvirial)/d(P)T",
"T", 300,
"P", 101325,
"n-Propane")));
1829TEST_CASE(
"Test second partial derivatives",
"[derivatives]") {
1831 SECTION(
"Check d2pdrho2|T 3 ways",
"") {
1833 double rhomolar = 60000;
1837 double d2pdrhomolar2__T_AbstractState =
1841 double d2pdrhomolar2__T_PropsSI_num =
1842 (
PropsSI(
"P",
"T",
T,
"Dmolar", rhomolar + del,
"Water") - 2 *
PropsSI(
"P",
"T",
T,
"Dmolar", rhomolar,
"Water")
1843 +
PropsSI(
"P",
"T",
T,
"Dmolar", rhomolar - del,
"Water"))
1845 double d2pdrhomolar2__T_PropsSI =
PropsSI(
"d(d(P)/d(Dmolar)|T)/d(Dmolar)|T",
"T",
T,
"Dmolar", rhomolar,
"Water");
1847 CAPTURE(d2pdrhomolar2__T_AbstractState);
1848 CAPTURE(d2pdrhomolar2__T_PropsSI_num);
1849 double rel_err_exact = std::abs((d2pdrhomolar2__T_AbstractState - d2pdrhomolar2__T_PropsSI) / d2pdrhomolar2__T_PropsSI);
1850 double rel_err_approx = std::abs((d2pdrhomolar2__T_PropsSI_num - d2pdrhomolar2__T_AbstractState) / d2pdrhomolar2__T_AbstractState);
1851 CHECK(rel_err_exact < 1e-5);
1852 CHECK(rel_err_approx < 1e-5);
1854 SECTION(
"Valid second partial derivatives",
"") {
1855 CHECK(
ValidNumber(
PropsSI(
"d(d(Hmolar)/d(P)|T)/d(T)|Dmolar",
"T", 300,
"P", 101325,
"n-Propane")));
1857 SECTION(
"Invalid second partial derivatives",
"") {
1858 CHECK(!
ValidNumber(
PropsSI(
"d(d()/d(P)|T)/d()|",
"T", 300,
"P", 101325,
"n-Propane")));
1859 CHECK(!
ValidNumber(
PropsSI(
"dd(Dmolar)/d()|T)|T",
"T", 300,
"P", 101325,
"n-Propane")));
1861 SECTION(
"Check derivatives with respect to T",
"") {
1863 double rhomolar = 100, dT = 1e-1;
1867 CoolPropDbl T0 = AS->T(), rhomolar0 = AS->rhomolar(), hmolar0 = AS->hmolar(), smolar0 = AS->smolar(), umolar0 = AS->umolar(), p0 = AS->p();
1879 CoolPropDbl Tpt = AS->T(), rhomolarpt = AS->rhomolar(), hmolarpt = AS->hmolar(), smolarpt = AS->smolar(), umolarpt = AS->umolar(),
1883 CoolPropDbl Tmt = AS->T(), rhomolarmt = AS->rhomolar(), hmolarmt = AS->hmolar(), smolarmt = AS->smolar(), umolarmt = AS->umolar(),
1886 CoolPropDbl dhdT_rho_num = (hmolarpt - hmolarmt) / (2 * dT);
1887 CoolPropDbl d2hdT2_rho_num = (hmolarpt - 2 * hmolar0 + hmolarmt) / pow(dT, 2);
1888 CoolPropDbl dsdT_rho_num = (smolarpt - smolarmt) / (2 * dT);
1889 CoolPropDbl d2sdT2_rho_num = (smolarpt - 2 * smolar0 + smolarmt) / pow(dT, 2);
1890 CoolPropDbl dudT_rho_num = (umolarpt - umolarmt) / (2 * dT);
1891 CoolPropDbl d2udT2_rho_num = (umolarpt - 2 * umolar0 + umolarmt) / pow(dT, 2);
1892 CoolPropDbl dpdT_rho_num = (ppt - pmt) / (2 * dT);
1893 CoolPropDbl d2pdT2_rho_num = (ppt - 2 * p0 + pmt) / pow(dT, 2);
1895 CAPTURE(
format(
"%0.15Lg", d2pdT2_rho_ana).c_str());
1898 CHECK(std::abs((dhdT_rho_num - dhdT_rho_ana) / dhdT_rho_ana) < tol);
1899 CHECK(std::abs((d2hdT2_rho_num - d2hdT2_rho_ana) / d2hdT2_rho_ana) < tol);
1900 CHECK(std::abs((dpdT_rho_num - dpdT_rho_ana) / dpdT_rho_ana) < tol);
1901 CHECK(std::abs((d2pdT2_rho_num - d2pdT2_rho_ana) / d2pdT2_rho_ana) < tol);
1902 CHECK(std::abs((dsdT_rho_num - dsdT_rho_ana) / dsdT_rho_ana) < tol);
1903 CHECK(std::abs((d2sdT2_rho_num - d2sdT2_rho_ana) / d2sdT2_rho_ana) < tol);
1904 CHECK(std::abs((dudT_rho_num - dudT_rho_ana) / dudT_rho_ana) < tol);
1905 CHECK(std::abs((d2udT2_rho_num - d2udT2_rho_ana) / d2udT2_rho_ana) < tol);
1908 SECTION(
"Check derivatives with respect to rho",
"") {
1910 double rhomolar = 100, drho = 1e-1;
1914 CoolPropDbl T0 = AS->T(), rhomolar0 = AS->rhomolar(), hmolar0 = AS->hmolar(), smolar0 = AS->smolar(), umolar0 = AS->umolar(), p0 = AS->p();
1926 CoolPropDbl Tpr = AS->T(), rhomolarpr = AS->rhomolar(), hmolarpr = AS->hmolar(), smolarpr = AS->smolar(), umolarpr = AS->umolar(),
1930 CoolPropDbl Tmr = AS->T(), rhomolarmr = AS->rhomolar(), hmolarmr = AS->hmolar(), smolarmr = AS->smolar(), umolarmr = AS->umolar(),
1933 CoolPropDbl dhdrho_T_num = (hmolarpr - hmolarmr) / (2 * drho);
1934 CoolPropDbl d2hdrho2_T_num = (hmolarpr - 2 * hmolar0 + hmolarmr) / pow(drho, 2);
1935 CoolPropDbl dsdrho_T_num = (smolarpr - smolarmr) / (2 * drho);
1936 CoolPropDbl d2sdrho2_T_num = (smolarpr - 2 * smolar0 + smolarmr) / pow(drho, 2);
1937 CoolPropDbl dudrho_T_num = (umolarpr - umolarmr) / (2 * drho);
1938 CoolPropDbl d2udrho2_T_num = (umolarpr - 2 * umolar0 + umolarmr) / pow(drho, 2);
1939 CoolPropDbl dpdrho_T_num = (ppr - pmr) / (2 * drho);
1940 CoolPropDbl d2pdrho2_T_num = (ppr - 2 * p0 + pmr) / pow(drho, 2);
1942 CAPTURE(
format(
"%0.15Lg", d2pdrho2_T_ana).c_str());
1945 CHECK(std::abs((dhdrho_T_num - dhdrho_T_ana) / dhdrho_T_ana) < tol);
1946 CHECK(std::abs((d2hdrho2_T_num - d2hdrho2_T_ana) / d2hdrho2_T_ana) < tol);
1947 CHECK(std::abs((dpdrho_T_num - dpdrho_T_ana) / dpdrho_T_ana) < tol);
1948 CHECK(std::abs((d2pdrho2_T_num - d2pdrho2_T_ana) / d2pdrho2_T_ana) < tol);
1949 CHECK(std::abs((dsdrho_T_num - dsdrho_T_ana) / dsdrho_T_ana) < tol);
1950 CHECK(std::abs((d2sdrho2_T_num - d2sdrho2_T_ana) / d2sdrho2_T_ana) < tol);
1951 CHECK(std::abs((dudrho_T_num - dudrho_T_ana) / dudrho_T_ana) < tol);
1952 CHECK(std::abs((d2udrho2_T_num - d2udrho2_T_ana) / d2udrho2_T_ana) < tol);
1954 SECTION(
"Check second mixed partial(h,p) with respect to rho",
"") {
1956 double dhmass = 1.0,
T = 300;
1963 double deriv_num = (deriv1 - deriv2) / dhmass;
1965 CAPTURE(deriv_analyt);
1968 CHECK(std::abs((deriv_num - deriv_analyt) / deriv_analyt) < tol);
1972TEST_CASE(
"REFPROP names for coolprop fluids",
"[REFPROPName]") {
1976 for (
const auto& fluid : fluids) {
1977 std::ostringstream ss1;
1978 ss1 <<
"Check that REFPROP fluid name for fluid " << fluid <<
" is valid";
1979 SECTION(ss1.str(),
"") {
1981 CHECK(!RPName.empty());
1983 if (!RPName.compare(
"N/A")) {
1991TEST_CASE(
"Backwards compatibility for REFPROP v4 fluid name convention",
"[REFPROP_backwards_compatibility]") {
1994 SECTION(
"REFPROP-",
"") {
1995 double val =
Props1SI(
"REFPROP-Water",
"Tcrit");
2001 SECTION(
"REFPROP-MIX:",
"") {
2002 double val =
PropsSI(
"T",
"P", 101325,
"Q", 0,
"REFPROP-MIX:Methane[0.5]&Ethane[0.5]");
2010class AncillaryFixture
2016 for (
const auto& fluid : fluids) {
2019 if (!rHEOS->is_pure()) {
2025 void do_sat(shared_ptr<CoolProp::AbstractState>& AS) {
2029 for (std::size_t k = 0; k < 3; ++k) {
2030 const double f = 0.1 + 0.4 *
static_cast<double>(k);
2031 double Tc = AS->T_critical();
2032 double Tt = AS->Ttriple();
2033 double T = f * Tc + (1 - f) * Tt;
2034 name =
strjoin(AS->fluid_names(),
"&");
2045 void check_pL(
const shared_ptr<CoolProp::AbstractState>& AS) {
2046 double p_EOS = AS->saturated_liquid_keyed_output(
iP);
2048 double err = std::abs(p_EOS - p_anc) / p_anc;
2056 void check_pV(
const shared_ptr<CoolProp::AbstractState>& AS) {
2057 double p_EOS = AS->saturated_liquid_keyed_output(
iP);
2059 double err = std::abs(p_EOS - p_anc) / p_anc;
2067 void check_rhoL(
const shared_ptr<CoolProp::AbstractState>& AS) {
2068 double rho_EOS = AS->saturated_liquid_keyed_output(
iDmolar);
2070 double err = std::abs(rho_EOS - rho_anc) / rho_anc;
2078 void check_rhoV(
const shared_ptr<CoolProp::AbstractState>& AS) {
2079 double rho_EOS = AS->saturated_vapor_keyed_output(
iDmolar);
2081 double err = std::abs(rho_EOS - rho_anc) / rho_anc;
2096TEST_CASE(
"Triple point checks",
"[triple_point]") {
2098 for (
const auto& fluid : fluids) {
2099 std::vector<std::string> names(1, fluid);
2100 shared_ptr<CoolProp::HelmholtzEOSMixtureBackend> HEOS = std::make_shared<CoolProp::HelmholtzEOSMixtureBackend>(names);
2102 if (!HEOS->is_pure()) {
2106 std::ostringstream ss1;
2107 ss1 <<
"Minimum saturation temperature state matches for liquid " << fluid;
2108 SECTION(ss1.str(),
"") {
2110 double p_EOS = HEOS->p();
2111 double p_sat_min_liquid = HEOS->get_components()[0].EOS().sat_min_liquid.p;
2112 double err_sat_min_liquid = std::abs(p_EOS - p_sat_min_liquid) / p_sat_min_liquid;
2114 CAPTURE(p_sat_min_liquid);
2115 CAPTURE(err_sat_min_liquid);
2119 CHECK(err_sat_min_liquid < 1e-3);
2121 std::ostringstream ss2;
2122 ss2 <<
"Minimum saturation temperature state matches for vapor " << fluid;
2123 SECTION(ss2.str(),
"") {
2126 double p_EOS = HEOS->p();
2127 double p_sat_min_vapor = HEOS->get_components()[0].EOS().sat_min_vapor.p;
2128 double err_sat_min_vapor = std::abs(p_EOS - p_sat_min_vapor) / p_sat_min_vapor;
2130 CAPTURE(p_sat_min_vapor);
2131 CAPTURE(err_sat_min_vapor);
2135 CHECK(err_sat_min_vapor < 1e-3);
2137 std::ostringstream ss3;
2138 ss3 <<
"Minimum saturation temperature state matches for vapor " << fluid;
2139 SECTION(ss3.str(),
"") {
2140 if (HEOS->p_triple() < 10) {
2145 double T_EOS = HEOS->T();
2146 double T_sat_min_vapor = HEOS->get_components()[0].EOS().sat_min_vapor.T;
2147 double err_sat_min_vapor = std::abs(T_EOS - T_sat_min_vapor);
2149 CAPTURE(T_sat_min_vapor);
2150 CAPTURE(err_sat_min_vapor);
2151 CHECK(err_sat_min_vapor < 1e-3);
2153 std::ostringstream ss4;
2154 ss4 <<
"Minimum saturation temperature state matches for liquid " << fluid;
2155 SECTION(ss4.str(),
"") {
2156 if (HEOS->p_triple() < 10) {
2160 double T_EOS = HEOS->T();
2161 double T_sat_min_vapor = HEOS->get_components()[0].EOS().sat_min_vapor.T;
2162 double err_sat_min_vapor = std::abs(T_EOS - T_sat_min_vapor);
2164 CAPTURE(T_sat_min_vapor);
2165 CAPTURE(err_sat_min_vapor);
2166 CHECK(err_sat_min_vapor < 1e-3);
2202 for (
const auto& fluid : fluids) {
2205 if (!rHEOS->is_pure()) {
2211 void do_sat(shared_ptr<CoolProp::AbstractState>& AS) {
2212 Tc = AS->T_critical();
2213 name =
strjoin(AS->fluid_names(),
"&");
2215 double Tt = AS->Ttriple();
2216 if (AS->fluid_param_string(
"pure") ==
"true") {
2217 Tc = std::min(Tc, AS->T_reducing());
2221 for (
double j = 0.1; j > 1e-10; j /= 10) {
2222 check_QT(AS, Tc - j);
2225 void check_at_Tc(
const shared_ptr<CoolProp::AbstractState>& AS) {
2226 CAPTURE(
"Check @ Tc");
2228 CHECK_NOTHROW(AS->update(
QT_INPUTS, 0, Tc));
2230 void check_QT(
const shared_ptr<CoolProp::AbstractState>& AS,
double T) {
2231 std::string test_name =
"Check --> Tc";
2238TEST_CASE_METHOD(SatTFixture,
"Test that saturation solvers solve all the way to T = Tc",
"[sat_T_to_Tc]") {
2242TEST_CASE(
"Check mixtures with fluid name aliases",
"[mixture_name_aliasing]") {
2243 shared_ptr<CoolProp::AbstractState> AS1, AS2;
2246 REQUIRE(AS1->fluid_names().size() == AS2->fluid_names().size());
2247 std::size_t N = AS1->fluid_names().size();
2248 for (std::size_t i = 0; i < N; ++i) {
2250 CHECK(AS1->fluid_names()[i] == AS2->fluid_names()[i]);
2254TEST_CASE(
"Predefined mixtures",
"[predefined_mixtures]") {
2255 SECTION(
"PropsSI") {
2256 double val =
PropsSI(
"Dmolar",
"P", 101325,
"T", 300,
"Air.mix");
2263TEST_CASE(
"Test that reference states yield proper values using high-level interface",
"[reference_states]") {
2267 double hmass, smass;
2273 std::string fluids[] = {
"n-Propane",
"R134a",
"R124"};
2274 ref_entry entries[3] = {{
"IIR", 200000, 1000,
"T", 273.15,
"Q", 0}, {
"ASHRAE", 0, 0,
"T", 233.15,
"Q", 0}, {
"NBP", 0, 0,
"P", 101325,
"Q", 0}};
2275 for (
const auto& fluid : fluids) {
2276 for (
auto& entry : entries) {
2277 std::ostringstream ss1;
2278 ss1 <<
"Check state for " << fluid <<
" for " + entry.name +
" reference state ";
2279 SECTION(ss1.str(),
"") {
2285 double hmass =
PropsSI(
"Hmass", entry.in1, entry.val1, entry.in2, entry.val2, fluid);
2286 double smass =
PropsSI(
"Smass", entry.in1, entry.val1, entry.in2, entry.val2, fluid);
2287 CHECK(std::abs(hmass - entry.hmass) < 1e-8);
2288 CHECK(std::abs(smass - entry.smass) < 1e-8);
2295TEST_CASE(
"Test that reference states yield proper values using low-level interface",
"[reference_states]") {
2299 double hmass, smass;
2305 std::string fluids[] = {
"n-Propane",
"R134a",
"R124"};
2306 ref_entry entries[3] = {{
"IIR", 200000, 1000,
iT, 273.15,
iQ, 0}, {
"ASHRAE", 0, 0,
iT, 233.15,
iQ, 0}, {
"NBP", 0, 0,
iP, 101325,
iQ, 0}};
2307 for (
const auto& fluid : fluids) {
2308 for (
auto& entry : entries) {
2309 std::ostringstream ss1;
2310 ss1 <<
"Check state for " << fluid <<
" for " + entry.name +
" reference state ";
2311 SECTION(ss1.str(),
"") {
2316 AS->update(pair, val1, val2);
2317 double hmass0 = AS->hmass();
2318 double smass0 = AS->smass();
2321 AS->update(pair, val1, val2);
2322 double hmass00 = AS->hmass();
2323 double smass00 = AS->smass();
2324 CHECK(std::abs(hmass00 - hmass0) < 1e-10);
2325 CHECK(std::abs(smass00 - smass0) < 1e-10);
2332 AS->update(pair, val1, val2);
2333 double hmass1 = AS->hmass();
2334 double smass1 = AS->smass();
2335 CHECK(std::abs(hmass1 - hmass0) < 1e-10);
2336 CHECK(std::abs(smass1 - smass0) < 1e-10);
2340 AS2->update(pair, val1, val2);
2341 double hmass2 = AS2->hmass();
2342 double smass2 = AS2->smass();
2343 CHECK(std::abs(hmass2 - entry.hmass) < 1e-8);
2344 CHECK(std::abs(smass2 - entry.smass) < 1e-8);
2353class FixedStateFixture
2356 void run_fluid(
const std::string& fluid,
const std::string& state,
const std::string& ref_state) {
2359 if (
Props1SI(
"Ttriple", fluid) > 233.15 && ref_state ==
"ASHRAE") {
2362 if (
Props1SI(
"Tcrit", fluid) < 233.15 && ref_state ==
"ASHRAE") {
2365 if (
Props1SI(
"Tcrit", fluid) < 273.15 && ref_state ==
"IIR") {
2368 if (
Props1SI(
"Ttriple", fluid) > 273.15 && ref_state ==
"IIR") {
2371 if (
Props1SI(
"ptriple", fluid) > 101325 && ref_state ==
"NBP") {
2376 if (ref_state !=
"DEF") {
2381 }
catch (std::exception& e) {
2389 std::ostringstream name;
2390 name <<
"Check state for " << state <<
" for " << fluid <<
" for reference state " << ref_state;
2391 CAPTURE(name.str());
2393 std::vector<std::string> fl(1, fluid);
2394 shared_ptr<CoolProp::HelmholtzEOSMixtureBackend> HEOS = std::make_shared<CoolProp::HelmholtzEOSMixtureBackend>(fl);
2397 if (HEOS->is_pure() && (state ==
"max_sat_T" || state ==
"max_sat_p")) {
2406 if ((fluid ==
"Water" || fluid ==
"CarbonDioxide") && (state ==
"reducing" || state ==
"critical")) {
2414 double EOS_hmolar = HEOS->hmolar();
2415 double EOS_smolar = HEOS->smolar();
2416 CAPTURE(EOS_hmolar);
2417 CAPTURE(EOS_smolar);
2418 CHECK(std::abs(EOS_hmolar - _state.
hmolar) < 1e-2);
2419 CHECK(std::abs(EOS_smolar - _state.
smolar) < 1e-2);
2426 for (
const auto& fluid : fluids) {
2427 std::string ref_state[4] = {
"DEF",
"IIR",
"ASHRAE",
"NBP"};
2428 for (
const auto& j : ref_state) {
2429 std::string states[] = {
"hs_anchor",
"reducing",
"critical",
"max_sat_T",
"max_sat_p",
"triple_liquid",
"triple_vapor"};
2430 for (
const auto& state : states) {
2431 run_fluid(fluid, state, j);
2437TEST_CASE_METHOD(FixedStateFixture,
"Test that enthalpies and entropies are correct for fixed states for all reference states",
"[fixed_states]") {
2441TEST_CASE(
"Check the first partial derivatives",
"[first_saturation_partial_deriv]") {
2442 const int number_of_pairs = 10;
2447 pair pairs[number_of_pairs] = {{
iP,
iT}, {
iDmolar,
iT}, {
iHmolar,
iT}, {
iSmolar,
iT}, {
iUmolar,
iT},
2450 for (
auto& pair : pairs) {
2452 std::ostringstream ss1;
2455 SECTION(ss1.str(),
"") {
2458 CoolPropDbl analytical = AS->first_saturation_deriv(pair.p1, pair.p2);
2459 CAPTURE(analytical);
2461 if (pair.p2 ==
iT) {
2466 numerical = (v1 - v2) / (2e-5);
2467 }
else if (pair.p2 ==
iP) {
2472 numerical = (v1 - v2) / (2e-2);
2477 CHECK(std::abs(numerical / analytical - 1) < 1e-4);
2482TEST_CASE(
"Check the second saturation derivatives",
"[second_saturation_partial_deriv]") {
2483 const int number_of_pairs = 5;
2488 pair pairs[number_of_pairs] = {{
iT,
iP,
iP}, {
iDmolar,
iP,
iP}, {
iHmolar,
iP,
iP}, {
iSmolar,
iP,
iP}, {
iUmolar,
iP,
iP}};
2490 for (
auto& pair : pairs) {
2492 std::ostringstream ss1;
2495 SECTION(ss1.str(),
"") {
2498 CoolPropDbl analytical = AS->second_saturation_deriv(pair.p1, pair.p2, pair.p3);
2499 CAPTURE(analytical);
2501 if (pair.p2 ==
iT) {
2503 }
else if (pair.p2 ==
iP) {
2505 CoolPropDbl v1 = AS->first_saturation_deriv(pair.p1, pair.p2);
2507 CoolPropDbl v2 = AS->first_saturation_deriv(pair.p1, pair.p2);
2508 numerical = (v1 - v2) / (2e-2);
2513 CHECK(std::abs(numerical / analytical - 1) < 1e-4);
2518TEST_CASE(
"Check the first two-phase derivative",
"[first_two_phase_deriv]") {
2519 const int number_of_pairs = 4;
2524 pair pairs[number_of_pairs] = {{
iDmass,
iP,
iHmass}, {
iDmolar,
iP,
iHmolar}, {
iDmolar,
iHmolar,
iP}, {
iDmass,
iHmass,
iP}};
2525 shared_ptr<CoolProp::HelmholtzEOSBackend> AS = std::make_shared<CoolProp::HelmholtzEOSBackend>(
"n-Propane");
2526 for (
auto& pair : pairs) {
2528 std::ostringstream ss1;
2531 SECTION(ss1.str(),
"") {
2534 CoolPropDbl analytical = AS->first_two_phase_deriv(pair.p1, pair.p2, pair.p3);
2535 CAPTURE(analytical);
2539 v2base = AS->keyed_output(pair.p2);
2540 v3base = AS->keyed_output(pair.p3);
2544 AS->update(input_pair1, out1, out2);
2547 AS->update(input_pair2, out1, out2);
2550 numerical = (v1 - v2) / (v2plus - v2minus);
2552 CHECK(std::abs(numerical / analytical - 1) < 1e-4);
2557TEST_CASE(
"Check the second two-phase derivative",
"[second_two_phase_deriv]") {
2558 SECTION(
"d2rhodhdp",
"") {
2559 shared_ptr<CoolProp::HelmholtzEOSBackend> AS = std::make_shared<CoolProp::HelmholtzEOSBackend>(
"n-Propane");
2562 CAPTURE(analytical);
2563 CoolPropDbl pplus = AS->p() * 1.001, pminus = AS->p() * 0.999, h = AS->hmolar();
2568 CoolPropDbl numerical = (v1 - v2) / (pplus - pminus);
2570 CHECK(std::abs(numerical / analytical - 1) < 1e-6);
2572 SECTION(
"d2rhodhdp using mass",
"") {
2573 shared_ptr<CoolProp::HelmholtzEOSBackend> AS = std::make_shared<CoolProp::HelmholtzEOSBackend>(
"n-Propane");
2576 CAPTURE(analytical);
2577 CoolPropDbl pplus = AS->p() * 1.001, pminus = AS->p() * 0.999, h = AS->hmass();
2582 CoolPropDbl numerical = (v1 - v2) / (pplus - pminus);
2584 CHECK(std::abs(numerical / analytical - 1) < 1e-6);
2588TEST_CASE(
"Check the first two-phase derivative using splines",
"[first_two_phase_deriv_splined]") {
2637 using paramtuple = std::tuple<parameters, parameters, parameters>;
2639 SECTION(
"Compared with reference data") {
2641 std::map<paramtuple, double> pairs = {{{
iDmass,
iP,
iHmass}, 0.00056718665544440146},
2646 for (
auto& [pair, expected_value] : pairs) {
2648 std::ostringstream ss1;
2649 auto& [p1, p2, p3] = pair;
2653 SECTION(ss1.str(),
"") {
2655 CoolPropDbl analytical = AS->first_two_phase_deriv_splined(p1, p2, p3, x_end);
2656 CAPTURE(analytical);
2657 CHECK(std::abs(expected_value / analytical - 1) < 1e-8);
2661 SECTION(
"Finite diffs") {
2664 for (
auto& pair : pairs) {
2666 std::ostringstream ss1;
2667 auto& [p1, p2, p3] = pair;
2671 SECTION(ss1.str(),
"") {
2674 CoolPropDbl analytical = AS->first_two_phase_deriv_splined(p1, p2, p3, x_end);
2675 CAPTURE(analytical);
2679 v2base = AS->keyed_output(p2);
2680 v3base = AS->keyed_output(p3);
2687 AS->update(input_pair1, out1, out2);
2688 CoolPropDbl D1 = AS->first_two_phase_deriv_splined(p1, p1, p1, x_end);
2693 AS->update(input_pair2, out1, out2);
2694 CoolPropDbl D2 = AS->first_two_phase_deriv_splined(p1, p1, p1, x_end);
2696 numerical = (D1 - D2) / (v2plus - v2minus);
2698 CHECK(std::abs(numerical / analytical - 1) < 1e-8);
2704TEST_CASE(
"Check the phase flags",
"[phase]") {
2705 SECTION(
"subcooled liquid") {
2710 SECTION(
"superheated gas") {
2715 SECTION(
"supercritical gas") {
2720 SECTION(
"supercritical liquid") {
2725 SECTION(
"supercritical") {
2732TEST_CASE(
"Check the changing of reducing function constants",
"[reducing]") {
2734 std::vector<double> z(2);
2739 AS1->set_mole_fractions(z);
2740 AS2->set_mole_fractions(z);
2741 std::vector<CoolProp::CriticalState> pts1 = AS1->all_critical_points();
2742 double gammaT = AS2->get_binary_interaction_double(0, 1,
"gammaT");
2743 AS2->set_binary_interaction_double(0, 1,
"gammaT", gammaT * 0.7);
2744 std::vector<CoolProp::CriticalState> pts2 = AS2->all_critical_points();
2745 double Tdiff = abs(pts2[0].
T - pts1[0].
T);
2746 CHECK(Tdiff > 1e-3);
2749TEST_CASE(
"Check the PC-SAFT pressure function",
"[pcsaft_pressure]") {
2751 double p_calc =
CoolProp::PropsSI(
"P",
"T", 320.,
"Dmolar", 9033.114359706229,
"PCSAFT::TOLUENE");
2752 CHECK(abs((p_calc / p) - 1) < 1e-5);
2754 p_calc =
CoolProp::PropsSI(
"P",
"T", 274.,
"Dmolar", 55530.40675319466,
"PCSAFT::WATER");
2755 CHECK(abs((p_calc / p) - 1) < 1e-5);
2757 p_calc =
CoolProp::PropsSI(
"P",
"T", 305.,
"Dmolar", 16965.6697209874,
"PCSAFT::ACETIC ACID");
2758 CHECK(abs((p_calc / p) - 1) < 1e-5);
2760 p_calc =
CoolProp::PropsSI(
"P",
"T", 240.,
"Dmolar", 15955.50941242,
"PCSAFT::DIMETHYL ETHER");
2761 CHECK(abs((p_calc / p) - 1) < 1e-5);
2763 p_calc =
CoolProp::PropsSI(
"P",
"T", 298.15,
"Dmolar", 9368.903838750752,
"PCSAFT::METHANOL[0.055]&CYCLOHEXANE[0.945]");
2764 CHECK(abs((p_calc / p) - 1) < 1e-5);
2771 double phase =
CoolProp::PropsSI(
"Phase",
"T", 100.,
"Dmolar", rho,
"PCSAFT::PROPANE");
2774 CHECK(abs((p_calc / p) - 1) < 1e-4);
2777TEST_CASE(
"Tkaczuk et al. (2020) cryogenic mixtures reproduce Table 8",
"[Tkaczuk]") {
2781 const double T = 200.0, rhomolar = 10000.0;
2782 std::vector<double> z(2, 0.5);
2791 struct NormalizeGuard
2807 {
"Helium&Neon", 18430775.292601},
2808 {
"Helium&Argon", 17128034.388363},
2809 {
"Neon&Argon", 15905875.375781},
2811 for (
auto& c : cases) {
2813 AS->set_mole_fractions(z);
2827 CHECK(std::abs(p / c.p_ref - 1) < 1e-10);
2842 double Tr_ref, rhor_ref;
2844 {
"Helium&Neon", 26.3675566619, 23988.632514},
2845 {
"Helium&Argon", 87.5390792465, 15166.941197},
2846 {
"Neon&Argon", 111.4722707992, 15642.518187},
2848 std::vector<double> znq{0.3, 0.7};
2849 for (
auto& c : redcases) {
2851 AS->set_mole_fractions(znq);
2853 CHECK(std::abs(AS->T_reducing() / c.Tr_ref - 1) < 1e-8);
2854 CHECK(std::abs(AS->rhomolar_reducing() / c.rhor_ref - 1) < 1e-6);
2858TEST_CASE(
"Check the PC-SAFT density function",
"[pcsaft_density]") {
2859 double den = 9033.114209728405;
2860 double den_calc =
CoolProp::PropsSI(
"Dmolar",
"T|liquid", 320.,
"P", 101325.,
"PCSAFT::TOLUENE");
2861 CHECK(abs((den_calc / den) - 1) < 1e-5);
2863 den = 55530.40512318346;
2864 den_calc =
CoolProp::PropsSI(
"Dmolar",
"T|liquid", 274.,
"P", 101325,
"PCSAFT::WATER");
2865 CHECK(abs((den_calc / den) - 1) < 1e-5);
2868 den_calc =
CoolProp::PropsSI(
"Dmolar",
"T|liquid", 305.,
"P", 101325,
"PCSAFT::ACETIC ACID");
2869 CHECK(abs((den_calc / den) - 1) < 2e-2);
2871 den = 15955.509146801696;
2872 den_calc =
CoolProp::PropsSI(
"Dmolar",
"T|liquid", 240.,
"P", 101325,
"PCSAFT::DIMETHYL ETHER");
2873 CHECK(abs((den_calc / den) - 1) < 1e-5);
2875 den = 9368.90368306872;
2876 den_calc =
CoolProp::PropsSI(
"Dmolar",
"T|liquid", 298.15,
"P", 101325,
"PCSAFT::METHANOL[0.055]&CYCLOHEXANE[0.945]");
2877 CHECK(abs((den_calc / den) - 1) < 1e-5);
2879 den = 55740.157290833515;
2881 CoolProp::PropsSI(
"Dmolar",
"T|liquid", 298.15,
"P", 101325,
"PCSAFT::Na+[0.010579869455908]&Cl-[0.010579869455908]&WATER[0.978840261088184]");
2882 CHECK(abs((den_calc / den) - 1) < 1e-5);
2885 den_calc =
CoolProp::PropsSI(
"Dmolar",
"T|liquid", 85.525,
"P", 1.7551e-4,
"PCSAFT::PROPANE");
2886 CHECK(abs((den_calc / den) - 1) < 1e-2);
2889 den_calc =
CoolProp::PropsSI(
"Dmolar",
"T|gas", 85.525,
"P", 1.39e-4,
"PCSAFT::PROPANE");
2890 CHECK(abs((den_calc / den) - 1) < 1e-2);
2893 den_calc =
CoolProp::PropsSI(
"Dmolar",
"T|liquid", 293,
"P", 833240,
"PCSAFT::PROPANE");
2894 CHECK(abs((den_calc / den) - 1) < 1e-2);
2897 den_calc =
CoolProp::PropsSI(
"Dmolar",
"T|liquid", 430,
"P", 2000000,
"PCSAFT::PROPANE");
2898 CHECK(abs((den_calc / den) - 1) < 1e-2);
2901TEST_CASE(
"Check the PC-SAFT residual enthalpy function",
"[pcsaft_enthalpy]") {
2902 double h = -36809.962122036086;
2903 double h_calc =
CoolProp::PropsSI(
"Hmolar_residual",
"T|liquid", 325.,
"Dmolar", 8983.377722763931,
"PCSAFT::TOLUENE");
2904 CHECK(abs((h_calc / h) - 1) < 1e-5);
2906 h = -362.6832840695562;
2907 h_calc =
CoolProp::PropsSI(
"Hmolar_residual",
"T|gas", 325.,
"Dmolar", 39.44490805826904,
"PCSAFT::TOLUENE");
2908 CHECK(abs((h_calc / h) - 1) < 1e-5);
2910 h = -38925.302571456035;
2911 h_calc =
CoolProp::PropsSI(
"Hmolar_residual",
"T|liquid", 325.,
"Dmolar", 16655.853047419932,
"PCSAFT::ACETIC ACID");
2912 CHECK(abs((h_calc / h) - 1) < 1e-5);
2914 h = -15393.870073928741;
2915 h_calc =
CoolProp::PropsSI(
"Hmolar_residual",
"T|gas", 325.,
"Dmolar", 85.70199446609787,
"PCSAFT::ACETIC ACID");
2916 CHECK(abs((h_calc / h) - 1) < 1e-5);
2918 h = -18242.128097841978;
2919 h_calc =
CoolProp::PropsSI(
"Hmolar_residual",
"T|liquid", 325.,
"Dmolar", 13141.475980937616,
"PCSAFT::DIMETHYL ETHER");
2920 CHECK(abs((h_calc / h) - 1) < 1e-5);
2922 h = -93.819615173017169;
2923 h_calc =
CoolProp::PropsSI(
"Hmolar_residual",
"T|gas", 325.,
"Dmolar", 37.963459290365265,
"PCSAFT::DIMETHYL ETHER");
2924 CHECK(abs((h_calc / h) - 1) < 1e-5);
2927 h =
CoolProp::PropsSI(
"Hmolar_residual",
"T|liquid", 325.,
"Dmolar", 8983.377722763931,
"HEOS::TOLUENE");
2928 h_calc =
CoolProp::PropsSI(
"Hmolar_residual",
"T|liquid", 325.,
"Dmolar", 8983.377722763931,
"PCSAFT::TOLUENE");
2929 CHECK(abs(h_calc - h) < 600.);
2931 h =
CoolProp::PropsSI(
"Hmolar_residual",
"T|gas", 325.,
"Dmolar", 39.44490805826904,
"HEOS::TOLUENE");
2932 h_calc =
CoolProp::PropsSI(
"Hmolar_residual",
"T|gas", 325.,
"Dmolar", 39.44490805826904,
"PCSAFT::TOLUENE");
2933 CHECK(abs(h_calc - h) < 600.);
2935 h =
CoolProp::PropsSI(
"Hmolar_residual",
"T|liquid", 325.,
"Dmolar", 54794.1,
"HEOS::WATER");
2936 h_calc =
CoolProp::PropsSI(
"Hmolar_residual",
"T|liquid", 325.,
"Dmolar", 54794.1,
"PCSAFT::WATER");
2937 CHECK(abs(h_calc - h) < 600.);
2939 h =
CoolProp::PropsSI(
"Hmolar_residual",
"T|gas", 325.,
"Dmolar", 0.370207,
"HEOS::WATER");
2940 h_calc =
CoolProp::PropsSI(
"Hmolar_residual",
"T|gas", 325.,
"Dmolar", 0.370207,
"PCSAFT::WATER");
2941 CHECK(abs(h_calc - h) < 600.);
2944TEST_CASE(
"Check the PC-SAFT residual entropy function",
"[pcsaft_entropy]") {
2946 double s = -50.81694890352192;
2947 double s_calc =
CoolProp::PropsSI(
"Smolar_residual",
"T|liquid", 325.,
"Dmolar", 8983.377722763931,
"PCSAFT::TOLUENE");
2948 CHECK(abs((s_calc / s) - 1) < 1e-5);
2950 s = -0.2929618646219797;
2951 s_calc =
CoolProp::PropsSI(
"Smolar_residual",
"T|gas", 325.,
"Dmolar", 39.44490805826904,
"PCSAFT::TOLUENE");
2952 CHECK(abs((s_calc / s) - 1) < 1e-5);
2954 s = -47.42736805661422;
2955 s_calc =
CoolProp::PropsSI(
"Smolar_residual",
"T|liquid", 325.,
"Dmolar", 16655.853047419932,
"PCSAFT::ACETIC ACID");
2956 CHECK(abs((s_calc / s) - 1) < 1e-5);
2958 s = -34.0021996393859;
2959 s_calc =
CoolProp::PropsSI(
"Smolar_residual",
"T|gas", 325.,
"Dmolar", 85.70199446609787,
"PCSAFT::ACETIC ACID");
2960 CHECK(abs((s_calc / s) - 1) < 1e-5);
2962 s = -26.42525828195748;
2963 s_calc =
CoolProp::PropsSI(
"Smolar_residual",
"T|liquid", 325.,
"Dmolar", 13141.475980937616,
"PCSAFT::DIMETHYL ETHER");
2964 CHECK(abs((s_calc / s) - 1) < 1e-5);
2966 s = -0.08427662199177874;
2967 s_calc =
CoolProp::PropsSI(
"Smolar_residual",
"T|gas", 325.,
"Dmolar", 37.963459290365265,
"PCSAFT::DIMETHYL ETHER");
2968 CHECK(abs((s_calc / s) - 1) < 1e-5);
2971 s =
CoolProp::PropsSI(
"Smolar_residual",
"T|liquid", 325.,
"Dmolar", 8983.377722763931,
"HEOS::TOLUENE");
2972 s_calc =
CoolProp::PropsSI(
"Smolar_residual",
"T|liquid", 325.,
"Dmolar", 8983.377722763931,
"PCSAFT::TOLUENE");
2973 CHECK(abs(s_calc - s) < 3.);
2975 s =
CoolProp::PropsSI(
"Smolar_residual",
"T|gas", 325.,
"Dmolar", 39.44490805826904,
"HEOS::TOLUENE");
2976 s_calc =
CoolProp::PropsSI(
"Smolar_residual",
"T|gas", 325.,
"Dmolar", 39.44490805826904,
"PCSAFT::TOLUENE");
2977 CHECK(abs(s_calc - s) < 3.);
2979 s =
CoolProp::PropsSI(
"Smolar_residual",
"T|liquid", 325.,
"Dmolar", 54794.1,
"HEOS::WATER");
2980 s_calc =
CoolProp::PropsSI(
"Smolar_residual",
"T|liquid", 325.,
"Dmolar", 54794.1,
"PCSAFT::WATER");
2981 CHECK(abs(s_calc - s) < 3.);
2983 s =
CoolProp::PropsSI(
"Smolar_residual",
"T|gas", 325.,
"Dmolar", 0.370207,
"HEOS::WATER");
2984 s_calc =
CoolProp::PropsSI(
"Smolar_residual",
"T|gas", 325.,
"Dmolar", 0.370207,
"PCSAFT::WATER");
2985 CHECK(abs(s_calc - s) < 3.);
2988TEST_CASE(
"Check the PC-SAFT residual gibbs energy function",
"[pcsaft_gibbs]") {
2994 double g = -20294.461258;
2995 double g_calc =
CoolProp::PropsSI(
"Gmolar_residual",
"T|liquid", 325.,
"Dmolar", 8983.377872003264,
"PCSAFT::TOLUENE");
2996 CHECK(abs((g_calc / g) - 1) < 1e-5);
2999 g_calc =
CoolProp::PropsSI(
"Gmolar_residual",
"T|gas", 325.,
"Dmolar", 39.44491269148218,
"PCSAFT::TOLUENE");
3000 CHECK(abs((g_calc / g) - 1) < 1e-5);
3003 g_calc =
CoolProp::PropsSI(
"Gmolar_residual",
"T|liquid", 325.,
"Dmolar", 16655.853314424,
"PCSAFT::ACETIC ACID");
3004 CHECK(abs((g_calc / g) - 1) < 1e-5);
3007 g_calc =
CoolProp::PropsSI(
"Gmolar_residual",
"T|gas", 325.,
"Dmolar", 85.70199446609787,
"PCSAFT::ACETIC ACID");
3008 CHECK(abs((g_calc / g) - 1) < 1e-5);
3011 g_calc =
CoolProp::PropsSI(
"Gmolar_residual",
"T|liquid", 325.,
"Dmolar", 13141.47619110254,
"PCSAFT::DIMETHYL ETHER");
3012 CHECK(abs((g_calc / g) - 1) < 1e-5);
3015 g_calc =
CoolProp::PropsSI(
"Gmolar_residual",
"T|gas", 325.,
"Dmolar", 37.96344503293008,
"PCSAFT::DIMETHYL ETHER");
3016 CHECK(abs((g_calc / g) - 1) < 1e-5);
3019TEST_CASE(
"PC-SAFT gibbsmolar_residual satisfies g_res = h_res - T*s_res (#1943)",
"[pcsaft_gibbs][1943]") {
3026 const double T = 100.0;
3028 const double g = AS->gibbsmolar_residual();
3029 const double h = AS->hmolar_residual();
3030 const double s = AS->smolar_residual();
3034 CHECK(std::abs(g - (h -
T * s)) < 1e-6);
3037TEST_CASE(
"Check vapor pressures calculated using PC-SAFT",
"[pcsaft_vapor_pressure]") {
3038 double vp = 3290651.18080112;
3039 double vp_calc =
CoolProp::PropsSI(
"P",
"T", 572.6667,
"Q", 0,
"PCSAFT::TOLUENE");
3040 CHECK(abs((vp_calc / vp) - 1) < 1e-3);
3042 vp = 66917.67387203;
3044 CHECK(abs((vp_calc / vp) - 1) < 1e-3);
3046 vp = 190061.78088909;
3048 CHECK(abs((vp_calc / vp) - 1) < 1e-3);
3052 CHECK(abs((vp_calc / vp) - 1) < 1e-3);
3061 CHECK(abs((vp_calc / vp) - 1) < 0.01);
3065 CHECK(abs((vp_calc / vp) - 1) < 0.01);
3068TEST_CASE(
"Check PC-SAFT interaction parameter functions",
"[pcsaft_binary_interaction]") {
3070 std::string CAS_aacid =
"64-19-7";
3075TEST_CASE(
"Check bubble pressures calculated using PC-SAFT",
"[pcsaft_bubble_pressure]") {
3078 double vp_calc =
CoolProp::PropsSI(
"P",
"T", 421.05,
"Q", 0,
"PCSAFT::METHANE[0.0252]&BENZENE[0.9748]");
3079 CHECK(abs((vp_calc / vp) - 1) < 1e-3);
3087 vp_calc =
CoolProp::PropsSI(
"P",
"T", 327.48,
"Q", 0,
"PCSAFT::METHANOL[0.3]&CYCLOHEXANE[0.7]");
3088 CHECK(abs((vp_calc / vp) - 1) < 1e-3);
3092 std::string CAS_aacid =
"64-19-7";
3099 vp = 274890.39985918;
3100 vp_calc =
CoolProp::PropsSI(
"P",
"T", 403.574,
"Q", 0,
"PCSAFT::WATER[0.9898662364]&ACETIC ACID[0.0101337636]");
3101 CHECK(abs((vp_calc / vp) - 1) < 1e-2);
3103 vp = 72915.92217342;
3104 vp_calc =
CoolProp::PropsSI(
"P",
"T", 372.774,
"Q", 0,
"PCSAFT::WATER[0.2691800943]&ACETIC ACID[0.7308199057]");
3105 CHECK(abs((vp_calc / vp) - 1) < 2e-2);
3108 vp_calc =
CoolProp::PropsSI(
"P",
"T", 298.15,
"Q", 0,
"PCSAFT::Na+[0.0907304774758426]&Cl-[0.0907304774758426]&WATER[0.818539045048315]");
3109 CHECK(abs((vp_calc / vp) - 1) < 0.23);
3112TEST_CASE(
"Check bubble temperatures calculated using PC-SAFT",
"[pcsaft_bubble_temperature]") {
3113 double t = 572.6667;
3114 double t_calc =
CoolProp::PropsSI(
"T",
"P", 3290651.18080112,
"Q", 0,
"PCSAFT::TOLUENE");
3115 CHECK(abs((t_calc / t) - 1) < 1e-3);
3119 CHECK(abs((t_calc / t) - 1) < 1e-3);
3122 t_calc =
CoolProp::PropsSI(
"T",
"P", 190061.78088909,
"Q", 0,
"PCSAFT::ACETIC ACID");
3123 CHECK(abs((t_calc / t) - 1) < 1e-3);
3126 t_calc =
CoolProp::PropsSI(
"T",
"P", 623027.07850612,
"Q", 0,
"PCSAFT::DIMETHYL ETHER");
3127 CHECK(abs((t_calc / t) - 1) < 1e-3);
3135 t_calc =
CoolProp::PropsSI(
"T",
"P", 96634.2439079,
"Q", 0,
"PCSAFT::METHANOL[0.3]&CYCLOHEXANE[0.7]");
3136 CHECK(abs((t_calc / t) - 1) < 1e-3);
3140 std::string CAS_aacid =
"64-19-7";
3148 t_calc =
CoolProp::PropsSI(
"T",
"P", 274890.39985918,
"Q", 0,
"PCSAFT::WATER[0.9898662364]&ACETIC ACID[0.0101337636]");
3149 CHECK(abs((t_calc / t) - 1) < 1e-3);
3152 t_calc =
CoolProp::PropsSI(
"T",
"P", 72915.92217342,
"Q", 0,
"PCSAFT::WATER[0.2691800943]&ACETIC ACID[0.7308199057]");
3153 CHECK(abs((t_calc / t) - 1) < 2e-3);
3156 t_calc =
CoolProp::PropsSI(
"T",
"P", 2387.42669687,
"Q", 0,
"PCSAFT::Na+[0.0907304774758426]&Cl-[0.0907304774758426]&WATER[0.818539045048315]");
3157 CHECK(abs((t_calc / t) - 1) < 1e-2);
3160TEST_CASE(
"Github issue #2470",
"[pureflash]") {
3161 auto fluide =
"Nitrogen";
3162 auto enthalpy = 67040.57857;
3163 auto pressure = 3368965.046;
3164 std::shared_ptr<CoolProp::AbstractState> AS(AbstractState::factory(
"HEOS", fluide));
3168 CHECK_NOTHROW(AS->update(
PT_INPUTS, pressure, Ts));
3169 AS->unspecify_phase();
3170 CHECK_NOTHROW(AS->update(
HmassP_INPUTS, enthalpy, pressure));
3171 auto Tfinal = AS->T();
3172 CHECK(Tfinal > AS->T_critical());
3175TEST_CASE(
"Github issue #2467",
"[pureflash]") {
3176 auto fluide =
"Pentane";
3177 std::shared_ptr<CoolProp::AbstractState> AS(AbstractState::factory(
"HEOS", fluide));
3179 double p1 = AS->p();
3181 double p2 = AS->p();
3183 double s1 = AS->smass();
3187TEST_CASE(
"Github issue #1870",
"[pureflash]") {
3188 auto fluide =
"Pentane";
3189 std::shared_ptr<CoolProp::AbstractState> AS(AbstractState::factory(
"HEOS", fluide));
3193TEST_CASE(
"Github issue #2447",
"[2447]") {
3194 double pvap =
PropsSI(
"P",
"T", 360 + 273.15,
"Q", 0,
"INCOMP::S800");
3195 double err = std::abs(pvap / 961e3 - 1);
3199TEST_CASE(
"Github issue #2558",
"[2558]") {
3200 double Tau =
CoolProp::PropsSI(
"Tau",
"Dmolar|gas", 200.0,
"T", 300.0,
"CarbonDioxide[0.5]&Hydrogen[0.5]");
3201 double Delta =
CoolProp::PropsSI(
"Delta",
"Dmolar|gas", 200.0,
"T", 300.0,
"CarbonDioxide[0.5]&Hydrogen[0.5]");
3202 CHECK(std::isfinite(Tau));
3203 CHECK(std::isfinite(Delta));
3206TEST_CASE(
"Github issue #2491",
"[2491]") {
3207 std::shared_ptr<CoolProp::AbstractState> AS(AbstractState::factory(
"HEOS",
"Xenon"));
3209 CHECK(std::isfinite(AS->rhomolar()));
3212TEST_CASE(
"Github issue #2608",
"[2608]") {
3213 std::shared_ptr<CoolProp::AbstractState> AS(AbstractState::factory(
"HEOS",
"CO2"));
3214 double pc = AS->p_critical();
3218 SECTION(
"Without phase") {
3219 AS->unspecify_phase();
3222 SECTION(
"With phase") {
3225 AS->unspecify_phase();
3229TEST_CASE(
"Github issue #2622",
"[2622]") {
3232 std::shared_ptr<CoolProp::AbstractState> AS(AbstractState::factory(
"HEOS",
"R123"));
3233 double pc = AS->p_critical();
3235 double Tt = AS->Ttriple();
3246template <
typename T>
3247std::vector<T>
linspace(
T start,
T end,
int num) {
3248 std::vector<T> linspaced;
3253 linspaced.push_back(start);
3257 T step = (end - start) / (num - 1);
3258 for (
int i = 0; i < num; ++i) {
3259 linspaced.push_back(start + step * i);
3264TEST_CASE(
"Github issue #2582",
"[2582]") {
3265 std::shared_ptr<CoolProp::AbstractState> AS(AbstractState::factory(
"HEOS",
"CO2"));
3266 double pc = AS->p_critical();
3268 double hmass_liq = AS->saturated_liquid_keyed_output(
iHmass);
3269 double hmass_vap = AS->saturated_vapor_keyed_output(
iHmass);
3273 for (
auto hmass :
linspace(100e3, 700e3, 1000)) {
3277 for (
auto hmass :
linspace(100e3, 700e3, 1000)) {
3283TEST_CASE(
"Github issue #2594",
"[2594]") {
3284 std::shared_ptr<CoolProp::AbstractState> AS(AbstractState::factory(
"HEOS",
"CO2"));
3285 auto p = 7377262.928140703;
3286 double pc = AS->p_critical();
3288 double Tsat = AS->T();
3289 double rholiq = AS->rhomolar();
3290 double umass_liq = AS->saturated_liquid_keyed_output(
iUmass);
3291 double umass_vap = AS->saturated_vapor_keyed_output(
iUmass);
3296 auto umass = 314719.5306503257;
3304 double rho1 = AS->rhomolar();
3305 double T1 = AS->T();
3306 double dumolardT_P = AS->first_partial_deriv(
iUmolar,
iT,
iP);
3307 double dpdrho_T = AS->first_partial_deriv(
iP,
iDmolar,
iT);
3312 double rho2 = AS->rhomolar();
3313 double T2 = AS->T();
3314 double dpdrho_T_imposed = AS->first_partial_deriv(
iP,
iDmolar,
iT);
3315 double dumolardT_P_imposed = AS->first_partial_deriv(
iUmolar,
iT,
iP);
3317 AS->unspecify_phase();
3321 BENCHMARK(
"dp/drho|T") {
3324 BENCHMARK(
"du/dT|p") {
3329TEST_CASE(
"CoolProp.jl tests",
"[2598]") {
3344 auto pcrit =
Props1SI(fluid,
"pcrit");
3345 auto Tcrit =
Props1SI(fluid,
"Tcrit");
3347 CAPTURE(
PhaseSI(
"P", pcrit + 50000,
"T", Tcrit + 3, fluid));
3348 CAPTURE(
PhaseSI(
"P", pcrit + 50000,
"T", Tcrit - 3, fluid));
3349 CAPTURE(
PhaseSI(
"P", pcrit - 50000,
"T", Tcrit + 3, fluid));
3351 CAPTURE(
PropsSI(
"Q",
"P", pcrit + 50000,
"T", Tcrit + 3, fluid));
3352 CAPTURE(
PropsSI(
"Q",
"P", pcrit + 50000,
"T", Tcrit - 3, fluid));
3353 CAPTURE(
PropsSI(
"Q",
"P", pcrit - 50000,
"T", Tcrit + 3, fluid));
3355 CHECK(
PhaseSI(
"P", pcrit + 50000,
"T", Tcrit + 3, fluid) ==
"supercritical");
3356 CHECK(
PhaseSI(
"P", pcrit + 50000,
"T", Tcrit - 3, fluid) ==
"supercritical_liquid");
3357 CHECK(
PhaseSI(
"P", pcrit - 50000,
"T", Tcrit + 3, fluid) ==
"supercritical_gas");
3361TEST_CASE(
"Check methanol EOS matches REFPROP 10",
"[2538]") {
3364 auto TNBP_RP =
PropsSI(
"T",
"P", 101325,
"Q", 0,
"REFPROP::METHANOL");
3365 auto TNBP_CP =
PropsSI(
"T",
"P", 101325,
"Q", 0,
"HEOS::METHANOL");
3366 CHECK(TNBP_RP == Catch::Approx(TNBP_CP).epsilon(1e-6));
3368 auto rhoL_RP =
PropsSI(
"D",
"T", 400,
"Q", 0,
"REFPROP::METHANOL");
3369 auto rhoL_CP =
PropsSI(
"D",
"T", 400,
"Q", 0,
"HEOS::METHANOL");
3370 CHECK(rhoL_RP == Catch::Approx(rhoL_CP).epsilon(1e-12));
3372 auto cp0_RP =
PropsSI(
"CP0MOLAR",
"T", 400,
"Dmolar", 1e-5,
"REFPROP::METHANOL");
3373 auto cp0_CP =
PropsSI(
"CP0MOLAR",
"T", 400,
"Dmolar", 1e-5,
"HEOS::METHANOL");
3374 CHECK(cp0_RP == Catch::Approx(cp0_CP).epsilon(1e-4));
3377TEST_CASE(
"Check phase determination for PC-SAFT backend",
"[pcsaft_phase]") {
3378 double den = 9033.114209728405;
3379 double den_calc =
CoolProp::PropsSI(
"Dmolar",
"T", 320.,
"P", 101325.,
"PCSAFT::TOLUENE");
3380 CHECK(abs((den_calc / den) - 1) < 1e-2);
3381 double phase =
CoolProp::PropsSI(
"Phase",
"T", 320.,
"P", 101325.,
"PCSAFT::TOLUENE");
3385 den_calc =
CoolProp::PropsSI(
"Dmolar",
"T", 320.,
"P", 1000.,
"PCSAFT::TOLUENE");
3386 CHECK(abs((den_calc / den) - 1) < 1e-2);
3391TEST_CASE(
"Check that indexes for mixtures are assigned correctly, especially for the association term",
"[pcsaft_indexes]") {
3398 std::string CAS_aacid =
"64-19-7";
3405 double t = 413.5385;
3406 double rho = 15107.481234283325;
3407 double p =
CoolProp::PropsSI(
"P",
"T", t,
"Dmolar", rho,
"PCSAFT::ACETIC ACID");
3409 CoolProp::PropsSI(
"P",
"T", t,
"Dmolar", rho,
"PCSAFT::ACETIC ACID[1.0]&WATER[0]");
3410 CHECK(abs((p_extra - p) / p * 100) < 1e-1);
3415 rho = 10657.129498214763;
3417 p_extra =
CoolProp::PropsSI(
"P",
"T", t,
"Dmolar", rho,
"PCSAFT::WATER[0]&FURFURAL[1.0]");
3418 CHECK(abs((p_extra - p) / p * 100) < 1e-1);
3423 rho = 55320.89616248148;
3426 "PCSAFT::Na+[0]&Cl-[0]&WATER[1.0]&DIMETHOXYMETHANE[0]");
3427 CHECK(abs((p_extra - p) / p * 100) < 1e-1);
3431class SuperAncillaryOnFixture
3435 const bool initial_value;
3441 ~SuperAncillaryOnFixture() {
3447class SuperAncillaryOffFixture
3451 const bool initial_value;
3457 ~SuperAncillaryOffFixture() {
3464class PropertyLimitsFixture
3468 const bool initial_value;
3472 ~PropertyLimitsFixture() {
3477TEST_CASE_METHOD(SuperAncillaryOnFixture,
"Check superancillary for water",
"[superanc]") {
3484 BENCHMARK(
"HEOS.clear()") {
3485 return rHEOS.
clear();
3487 BENCHMARK(
"HEOS rho(T)") {
3488 return AS->update(
QT_INPUTS, 1.0, 300.0);
3490 BENCHMARK(
"HEOS update_QT_pure_superanc(Q,T)") {
3491 return rHEOS.update_QT_pure_superanc(1.0, 300.0);
3493 BENCHMARK(
"superanc rho(T)") {
3494 return anc.eval_sat(300.0,
'D', 1);
3496 BENCHMARK(
"IF97 rho(T)") {
3497 return IF97->update(
QT_INPUTS, 1.0, 300.0);
3500 double Tmin = AS->get_fluid_parameter_double(0,
"SUPERANC::Tmin");
3501 double Tc = AS->get_fluid_parameter_double(0,
"SUPERANC::Tcrit_num");
3502 double pmin = AS->get_fluid_parameter_double(0,
"SUPERANC::pmin");
3503 double pmax = AS->get_fluid_parameter_double(0,
"SUPERANC::pmax");
3505 CHECK_THROWS(AS->get_fluid_parameter_double(1,
"SUPERANC::pmax"));
3507 BENCHMARK(
"HEOS rho(p)") {
3508 return AS->update(
PQ_INPUTS, 101325, 1.0);
3510 BENCHMARK(
"superanc T(p)") {
3511 return anc.get_T_from_p(101325);
3513 BENCHMARK(
"IF97 rho(p)") {
3514 return IF97->update(
PQ_INPUTS, 101325, 1.0);
3518TEST_CASE_METHOD(SuperAncillaryOnFixture,
"Benchmark class construction",
"[superanc]") {
3520 BENCHMARK(
"Water [SA]") {
3523 BENCHMARK(
"R410A [no SA]") {
3526 BENCHMARK(
"propane [SA]") {
3529 BENCHMARK(
"air, pseudo-pure [SA]") {
3534TEST_CASE_METHOD(SuperAncillaryOffFixture,
"Check superancillary-like calculations with superancillary disabled for water",
"[superanc]") {
3540 auto& approxrhoL = anc.get_approx1d(
'D', 0);
3542 BENCHMARK(
"HEOS rho(T)") {
3543 return AS->update(
QT_INPUTS, 1.0, 300.0);
3545 BENCHMARK(
"superanc rho(T)") {
3546 return anc.eval_sat(300.0,
'D', 1);
3548 BENCHMARK(
"superanc rho(T) with expansion directly") {
3549 return approxrhoL.eval(300.0);
3551 BENCHMARK(
"superanc get_index rho(T)") {
3552 return approxrhoL.get_index(300.0);
3554 BENCHMARK(
"IF97 rho(T)") {
3555 return IF97->update(
QT_INPUTS, 1.0, 300.0);
3558 BENCHMARK(
"HEOS rho(p)") {
3559 return AS->update(
PQ_INPUTS, 101325, 1.0);
3561 BENCHMARK(
"superanc T(p)") {
3562 return anc.get_T_from_p(101325);
3564 BENCHMARK(
"IF97 rho(p)") {
3565 return IF97->update(
PQ_INPUTS, 101325, 1.0);
3569TEST_CASE_METHOD(SuperAncillaryOnFixture,
"Check superancillary functions are available for all pure fluids",
"[ancillary]") {
3573 if (rHEOS.is_pure()) {
3580 rHEOS.update_QT_pure_superanc(1, rHEOS.T_critical() * 0.9999);
3582 if (std::string(e.
what()).find(
"Superancillaries not available") != std::string::npos) {
3585 CHECK_NOTHROW((
void)(
"rethrow"));
3598TEST_CASE(
"Superancillary source_eos_hash matches current EOS at bit level",
"[ancillary]") {
3628 static const std::set<std::string> known_stale_SA = {};
3682 uint64_t h = 0xcbf29ce484222325ULL;
3684 void mix_u8(uint8_t b) {
3686 h *= 0x100000001b3ULL;
3689 void mix_bytes(
const void* data, std::size_t n) {
3690 const auto* p =
static_cast<const uint8_t*
>(data);
3691 for (std::size_t i = 0; i < n; ++i)
3696 void mix_u64(uint64_t v) {
3697 for (
int i = 0; i < 8; ++i)
3698 mix_u8(
static_cast<uint8_t
>((v >> (i * 8)) & 0xff));
3702 void walk(
const nlohmann::json& j) {
3705 }
else if (j.is_boolean()) {
3709 mix_u8(j.get<
bool>() ?
't' :
'f');
3710 }
else if (j.is_number_integer() || j.is_number_unsigned()) {
3716 mix_u64(
static_cast<uint64_t
>(j.get<int64_t>()));
3717 }
else if (j.is_number_float()) {
3724 double v = j.get<
double>();
3725 std::memcpy(&bits, &v, 8);
3727 }
else if (j.is_string()) {
3731 const auto& s = j.get_ref<
const std::string&>();
3734 mix_bytes(s.data(), s.size());
3735 }
else if (j.is_array()) {
3740 for (
const auto& el : j)
3742 }
else if (j.is_object()) {
3751 for (
auto it = j.begin(); it != j.end(); ++it) {
3752 const auto& k = it.key();
3754 mix_bytes(k.data(), k.size());
3760 std::string hex()
const {
3762 std::snprintf(buf,
sizeof(buf),
"%016llx",
static_cast<unsigned long long>(h));
3775 nlohmann::json fixture = {
3776 {
"alphar", {{{
"d", {1, 2, 3}}, {
"n", {-0.5, 1.25e-10, 3.14159265358979}}}}},
3777 {
"empty_array", nlohmann::json::array()},
3778 {
"empty_string",
""},
3779 {
"flag_false",
false},
3780 {
"flag_true",
true},
3781 {
"gas_constant", 8.3144598},
3782 {
"nested", {{
"deep", {{
"deeper",
nullptr}}}}},
3783 {
"zero_float", 0.0},
3786 TreeHasher fixture_hasher;
3787 fixture_hasher.walk(fixture);
3788 CHECK(fixture_hasher.hex() ==
"8e75626511d00b5c");
3795 int fluids_checked = 0;
3796 for (
const auto& jfluid : all_fluids) {
3797 if (!jfluid.contains(
"EOS") || jfluid.at(
"EOS").empty()) {
3800 const auto& eos = jfluid.at(
"EOS")[0];
3801 if (!eos.contains(
"SUPERANCILLARY")) {
3804 std::string name = jfluid.value(
"INFO", nlohmann::json::object()).value(
"NAME", std::string(
"?"));
3806 const auto& jsuper = eos.at(
"SUPERANCILLARY");
3813 REQUIRE(jsuper.contains(
"source_eos_hash"));
3814 auto stored = jsuper.at(
"source_eos_hash").get<std::string>();
3815 auto stripped = eos;
3816 stripped.erase(
"SUPERANCILLARY");
3819 auto computed = th.hex();
3822 if (known_stale_SA.count(name)) {
3825 CHECK(computed != stored);
3827 CHECK(computed == stored);
3831 CHECK(fluids_checked > 0);
3834TEST_CASE_METHOD(SuperAncillaryOnFixture,
"Superancillary eval matches extended-precision check points for all fluids",
"[ancillary]") {
3840 const double safety_factor = 4.0;
3841 const double floor_tol = 1e-14;
3842 int fluids_checked = 0;
3845 if (!jfluid.at(
"EOS")[0].contains(
"SUPERANCILLARY")) {
3849 auto jsuper = jfluid.at(
"EOS")[0].at(
"SUPERANCILLARY");
3853 REQUIRE(jsuper.contains(
"check_points"));
3855 for (
const auto& pt : anc.get_check_points()) {
3857 const double tol_p = std::max(std::abs(pt.p_SA_ratio - 1.0), floor_tol) * safety_factor;
3858 const double tol_rhoL = std::max(std::abs(pt.rhoL_SA_ratio - 1.0), floor_tol) * safety_factor;
3859 const double tol_rhoV = std::max(std::abs(pt.rhoV_SA_ratio - 1.0), floor_tol) * safety_factor;
3860 CHECK(std::abs(anc.eval_sat(pt.T,
'D', 0) / pt.rhoL - 1) < tol_rhoL);
3861 CHECK(std::abs(anc.eval_sat(pt.T,
'D', 1) / pt.rhoV - 1) < tol_rhoV);
3862 CHECK(std::abs(anc.eval_sat(pt.T,
'P', 1) / pt.p - 1) < tol_p);
3867 CHECK(fluids_checked > 0);
3870TEST_CASE_METHOD(SuperAncillaryOnFixture,
"Check out of bound for superancillary",
"[superanc]") {
3872 CHECK_THROWS(AS->update(
PQ_INPUTS, 100000000001325, 1.0));
3873 CHECK_THROWS(AS->update(
QT_INPUTS, 1.0, 1000000));
3876TEST_CASE_METHOD(SuperAncillaryOnFixture,
"Check throws for R410A",
"[superanc]") {
3879 CHECK_THROWS(rHEOS.update_QT_pure_superanc(1.0, 300.0));
3882TEST_CASE_METHOD(SuperAncillaryOnFixture,
"Check throws for REFPROP",
"[superanc]") {
3885 CHECK_THROWS(AS->update_QT_pure_superanc(1.0, 300.0));
3888TEST_CASE_METHOD(SuperAncillaryOnFixture,
"Check Tc & pc",
"[superanccrit]") {
3891 auto TcSA = AS->T_critical();
3892 auto pcSA = AS->p_critical();
3893 auto rhocSA = AS->rhomolar_critical();
3895 auto TcnonSA = AS->T_critical();
3896 auto pcnonSA = AS->p_critical();
3897 auto rhocnonSA = AS->rhomolar_critical();
3898 CHECK(TcSA != TcnonSA);
3899 CHECK(pcSA != pcnonSA);
3900 CHECK(rhocSA != rhocnonSA);
3903TEST_CASE_METHOD(SuperAncillaryOnFixture,
"Check h_fg",
"[superanc]") {
3905 CHECK_THROWS(AS->saturated_vapor_keyed_output(
iHmolar) - AS->saturated_liquid_keyed_output(
iHmolar));
3906 AS->update_QT_pure_superanc(1, 300);
3907 CHECK_NOTHROW(AS->saturated_vapor_keyed_output(
iHmolar) - AS->saturated_liquid_keyed_output(
iHmolar));
3910TEST_CASE_METHOD(SuperAncillaryOnFixture,
"Performance regression; on",
"[2438]") {
3912 BENCHMARK(
"HP regression") {
3917 std::cout << AS->Q() <<
'\n';
3919TEST_CASE_METHOD(SuperAncillaryOffFixture,
"Performance regression; off",
"[2438]") {
3921 BENCHMARK(
"HP regression") {
3926 std::cout << AS->Q() <<
'\n';
3928TEST_CASE_METHOD(SuperAncillaryOnFixture,
"Performance regression for TS; on",
"[2438saontime]") {
3932 auto sL = AS->saturated_liquid_keyed_output(
iSmolar);
3933 auto sV = AS->saturated_vapor_keyed_output(
iSmolar);
3935 for (
auto i = 0; i < N; ++i) {
3938 CHECK(AS->T() != 0);
3941TEST_CASE_METHOD(SuperAncillaryOnFixture,
"Performance regression for TS; on",
"[2438]") {
3945 auto sL = AS->saturated_liquid_keyed_output(
iSmolar);
3946 auto sV = AS->saturated_vapor_keyed_output(
iSmolar);
3947 BENCHMARK(
"ST regression") {
3953TEST_CASE_METHOD(SuperAncillaryOffFixture,
"Performance regression for TS; off",
"[2438]") {
3957 auto sL = AS->saturated_liquid_keyed_output(
iSmolar);
3958 auto sV = AS->saturated_vapor_keyed_output(
iSmolar);
3959 BENCHMARK(
"ST regression") {
3965TEST_CASE_METHOD(SuperAncillaryOnFixture,
"Benchmarking caching options",
"[caching]") {
3966 std::array<double, 16> buf15;
3968 std::array<double, 100> buf100;
3970 std::array<bool, 100> bool100;
3971 bool100.fill(
false);
3972 std::vector<CachedElement> cache100(100);
3973 for (
auto& i : cache100) {
3977 std::vector<std::optional<double>> opt100(100);
3978 for (
auto& i : opt100) {
3982 BENCHMARK(
"memset array15 w/ 0") {
3983 std::memset(buf15.data(), 0,
sizeof(buf15));
3986 BENCHMARK(
"std::fill_n array15") {
3987 std::fill_n(buf15.data(), 15, _HUGE);
3990 BENCHMARK(
"std::fill array15") {
3991 std::fill(buf15.begin(), buf15.end(), _HUGE);
3994 BENCHMARK(
"array15.fill()") {
3998 BENCHMARK(
"memset array100 w/ 0") {
3999 memset(buf100.data(), 0,
sizeof(buf100));
4002 BENCHMARK(
"memset bool100 w/ 0") {
4003 memset(bool100.data(),
false,
sizeof(bool100));
4006 BENCHMARK(
"std::fill_n array100") {
4007 std::fill_n(buf100.data(), 100, _HUGE);
4010 BENCHMARK(
"fill array100") {
4014 BENCHMARK(
"fill cache100") {
4015 for (
auto& i : cache100) {
4020 BENCHMARK(
"fill opt100") {
4021 for (
auto& i : opt100) {
4027std::vector<std::tuple<double, double, double, double>> MSA22values = {
4028 {200, 199.97, 142.56, 1.29559},
4029 {300, 300.19, 214.07, 1.70203},
4030 {400, 400.98, 286.16, 1.99194},
4031 {500, 503.02, 359.49, 2.21952},
4034TEST_CASE(
"Ideal gas thermodynamic properties",
"[2589]") {
4046 double pig = 101325;
4051 for (
auto [T_K, h_kJkg, u_kJkg, s_kJkgK] : MSA22values) {
4052 double rho = pig / (AS->gas_constant() * T_K);
4056 CHECK(AS->smass_idealgas() / AS->gas_constant() == Catch::Approx(RP->smass_idealgas() / AS->gas_constant()));
4057 CHECK(AS->hmass_idealgas() / AS->gas_constant() == Catch::Approx(RP->hmass_idealgas() / AS->gas_constant()));
4059 std::vector<double> mf(20, 1.0);
4060 auto o = rRP.call_THERM0dll(T_K, rho / 1e3, mf);
4061 CHECK(o.hmol_Jmol == Catch::Approx(RP->hmolar_idealgas()).epsilon(1e-12));
4062 CHECK(o.smol_JmolK == Catch::Approx(RP->smolar_idealgas()).epsilon(1e-12));
4063 CHECK(o.umol_Jmol == Catch::Approx(RP->umolar_idealgas()).epsilon(1e-12));
4066 CAPTURE(AS->hmass_idealgas());
4067 CAPTURE(AS->hmass_idealgas() - h_kJkg * 1e3);
4068 CAPTURE(AS->smass_idealgas());
4069 CAPTURE(AS->smass_idealgas() - s_kJkgK * 1e3);
4070 CAPTURE(AS->umass_idealgas());
4071 CAPTURE(AS->umass_idealgas() - u_kJkg * 1e3);
4074TEST_CASE_METHOD(SuperAncillaryOnFixture,
"Phase for solid water should throw",
"[2639]") {
4076 for (
auto p_Pa :
linspace(AS->p_triple() * 1.0001, AS->pmax(), 1000)) {
4078 auto Tm = AS->melting_line(
iT,
iP, p_Pa);
4080 CHECK_THROWS(AS->update(
PT_INPUTS, p_Pa, -5 + Tm));
4089TEST_CASE_METHOD(PropertyLimitsFixture,
"Below-melting PT guard honors DONT_CHECK_PROPERTY_LIMITS",
"[1936]") {
4091 const double Tt = AS->Ttriple();
4092 const double pc = AS->p_critical();
4099 const double p_sub = 0.5 * (AS->p_triple() + pc);
4100 const double p_sup = 5 * pc;
4105 for (
const Case& c : {Case{p_sub, Tt}, Case{p_sup, Tt}, Case{1e8, 230.0}}) {
4108 const double Tm = AS->melting_line(
iT,
iP, c.p_Pa);
4110 REQUIRE(c.T < Tm - 0.001);
4114 CHECK_THROWS(AS->update(
PT_INPUTS, c.p_Pa, c.T));
4119 CHECK_NOTHROW(AS->update(
PT_INPUTS, c.p_Pa, c.T));
4121 CHECK(AS->T() == Catch::Approx(c.T).epsilon(1e-12));
4122 CHECK(AS->p() == Catch::Approx(c.p_Pa).epsilon(1e-12));
4124 CHECK(AS->rhomolar() > 0);
4129TEST_CASE(
"PT update below melting-line pmin succeeds without specify_phase for all HEOS fluids",
"[melting]") {
4139 for (
const auto& name : all_fluids) {
4140 std::shared_ptr<AbstractState> AS;
4142 AS.reset(AbstractState::factory(
"HEOS", name));
4146 if (!AS->has_melting_line())
continue;
4148 const double pmin = AS->melting_line(
iP_min, -1, -1);
4150 const double p_test = 0.99 * pmin;
4152 const double T_test = AS->Ttriple() * 1.1;
4158 CHECK_NOTHROW(AS->update(
PT_INPUTS, p_test, T_test));
4160 CHECK(AS->rhomolar() > 0);
4165TEST_CASE(
"HeavyWater (D2O) melting line matches Herrig et al. check values",
"[melting]") {
4171 std::shared_ptr<AbstractState> AS(AbstractState::factory(
"HEOS",
"HeavyWater"));
4172 REQUIRE(AS->has_melting_line());
4184 {270.0, 0.837888413e2 * 1e6},
4185 {255.0, 0.236470168e3 * 1e6},
4186 {275.0, 0.619526971e3 * 1e6},
4187 {300.0, 0.959203594e3 * 1e6},
4189 for (
const Pt& pt : pts) {
4190 CAPTURE(pt.T_K, pt.p_Pa);
4191 const double T = AS->melting_line(
iT,
iP, pt.p_Pa);
4192 CHECK(
T == Catch::Approx(pt.T_K).epsilon(1e-6));
4196TEST_CASE(
"REFPROP melting_line honors iP_min/iT_min/iP_max/iT_max sentinels",
"[melting][REFPROP]") {
4201 double pmin = RP->melting_line(
iP_min, -1, -1);
4202 double Tmin = RP->melting_line(
iT_min, -1, -1);
4205 CHECK(pmin == Catch::Approx(RP->p_triple()));
4206 CHECK(pmin == Catch::Approx(HE->melting_line(
iP_min, -1, -1)).epsilon(1e-3));
4211 CHECK(Tmin == Catch::Approx(251.165).epsilon(1e-5));
4212 CHECK(Tmin < RP->Ttriple());
4214 CHECK_NOTHROW(RP->melting_line(
iP,
iT, Tmin));
4217 double pmax = RP->melting_line(
iP_max, -1, -1);
4218 double Tmax = RP->melting_line(
iT_max, -1, -1);
4225 CHECK(RP->melting_line(
iT,
iP, 1e8) == Catch::Approx(HE->melting_line(
iT,
iP, 1e8)).epsilon(1e-3));
4233TEST_CASE(
"Cubic superancillary saturation_ancillary accuracy vs EOS flash",
"[cubic_superanc][2739]") {
4234 for (
const auto& backend : std::vector<std::string>{
"PR",
"SRK"}) {
4241 SECTION(backend +
" accuracy across T range (0.3 to 0.99 of superanc Tc)") {
4242 for (
double frac : {0.3, 0.5, 0.7, 0.8, 0.9, 0.95, 0.99}) {
4243 double T = frac * Tc_sa;
4246 double p_eos = AS->p();
4247 double rhoL_eos = AS->saturated_liquid_keyed_output(
iDmolar);
4248 double rhoV_eos = AS->saturated_vapor_keyed_output(
iDmolar);
4250 double p_anc = ACB.calc_saturation_ancillary(
iP, 0,
iT,
T);
4251 double rhoL_anc = ACB.calc_saturation_ancillary(
iDmolar, 0,
iT,
T);
4252 double rhoV_anc = ACB.calc_saturation_ancillary(
iDmolar, 1,
iT,
T);
4261 CHECK(std::abs(p_anc - p_eos) / p_eos < 1e-3);
4262 CHECK(std::abs(rhoL_anc - rhoL_eos) / rhoL_eos < 1e-3);
4263 CHECK(std::abs(rhoV_anc - rhoV_eos) / rhoV_eos < 1e-3);
4270 SECTION(backend +
" physically reasonable very close to superanc Tc") {
4273 double pc_sa = ACB.calc_saturation_ancillary(
iP, 0,
iT, Tc_sa * (1.0 - 1e-7));
4274 for (
double frac : {0.9999, 0.99999, 0.999999, 1.0 - 1e-7}) {
4275 double T = frac * Tc_sa;
4277 double p_anc = ACB.calc_saturation_ancillary(
iP, 0,
iT,
T);
4278 double rhoL_anc = ACB.calc_saturation_ancillary(
iDmolar, 0,
iT,
T);
4279 double rhoV_anc = ACB.calc_saturation_ancillary(
iDmolar, 1,
iT,
T);
4284 CHECK(rhoL_anc > rhoV_anc);
4285 CHECK(std::abs(p_anc - pc_sa) / pc_sa < 0.01);
4291TEST_CASE(
"Cubic superancillary update_QT_pure_superanc",
"[cubic_superanc][2739]") {
4292 for (
const auto& backend : std::vector<std::string>{
"PR",
"SRK"}) {
4298 SECTION(backend +
" update_QT_pure_superanc consistency at several T") {
4299 for (
double frac : {0.5, 0.7, 0.9, 0.9999, 0.99999, 1.0 - 1e-7}) {
4300 double T = frac * Tc_sa;
4302 CHECK_NOTHROW(AS->update_QT_pure_superanc(0.5,
T));
4303 CHECK(std::abs(AS->T() -
T) < 1e-10);
4308 SECTION(backend +
" update_QT_pure_superanc Q=0 and Q=1 densities bracket Q=0.5") {
4309 double T = 0.8 * Tc_sa;
4310 AS->update_QT_pure_superanc(0.0,
T);
4311 double rhoL = AS->rhomolar();
4312 AS->update_QT_pure_superanc(1.0,
T);
4313 double rhoV = AS->rhomolar();
4314 AS->update_QT_pure_superanc(0.5,
T);
4315 double rhoM = AS->rhomolar();
4325TEST_CASE(
"Cubic pure-fluid DmolarT/DmassT round-trip vs PT",
"[cubic_DmolarT][2673]") {
4326 for (
const auto& backend : std::vector<std::string>{
"PR",
"SRK"}) {
4332 SECTION(backend +
" liquid round-trip subcooled") {
4336 const double T_in = 0.7 * Tc_sa;
4337 const double p_sat = ACB.calc_saturation_ancillary(
iP, 0,
iT, T_in);
4338 const double p_in = 2.0 * p_sat;
4340 const double rho_molar = AS->rhomolar();
4341 const double rho_mass = AS->rhomass();
4344 CHECK(AS->p() == Catch::Approx(p_in).epsilon(1e-6));
4345 CHECK(AS->T() == Catch::Approx(T_in).epsilon(1e-10));
4346 CHECK(AS->rhomolar() == Catch::Approx(rho_molar).epsilon(1e-12));
4350 CHECK(AS->p() == Catch::Approx(p_in).epsilon(1e-6));
4351 CHECK(AS->rhomass() == Catch::Approx(rho_mass).epsilon(1e-12));
4355 SECTION(backend +
" vapor round-trip below Tsat") {
4357 const double T_in = 0.7 * Tc_sa;
4358 const double p_sat = ACB.calc_saturation_ancillary(
iP, 1,
iT, T_in);
4359 const double p_in = 0.5 * p_sat;
4361 const double rho_molar = AS->rhomolar();
4363 CHECK(AS->p() == Catch::Approx(p_in).epsilon(1e-6));
4367 SECTION(backend +
" two-phase region returns iphase_twophase") {
4368 const double T_in = 0.7 * Tc_sa;
4369 const double rhoL = ACB.calc_saturation_ancillary(
iDmolar, 0,
iT, T_in);
4370 const double rhoV = ACB.calc_saturation_ancillary(
iDmolar, 1,
iT, T_in);
4371 const double p_sat = ACB.calc_saturation_ancillary(
iP, 0,
iT, T_in);
4372 const double rho_mid = 0.5 * (rhoL + rhoV);
4375 CHECK(AS->p() == Catch::Approx(p_sat).epsilon(1e-6));
4376 CHECK(AS->Q() > 0.0);
4377 CHECK(AS->Q() < 1.0);
4380 SECTION(backend +
" supercritical T returns single phase") {
4381 const double T_in = 1.5 * Tc_sa;
4382 const double p_in = 5e6;
4384 const double rho_molar = AS->rhomolar();
4386 CHECK(AS->p() == Catch::Approx(p_in).epsilon(1e-6));
4387 const auto ph = AS->phase();
4399TEST_CASE(
"Lemmon-IJT-2022 R1234yf pure fluid check values",
"[R1234yf],[Lemmon-IJT-2022]") {
4400 const double tol = 1e-4;
4404 SECTION(
"T=280 K, rho->0 (ideal-gas limit)") {
4406 CAPTURE(AS->cvmolar());
4407 CAPTURE(AS->cpmolar());
4408 CAPTURE(AS->speed_sound());
4409 CHECK(AS->cvmolar() == Catch::Approx(89.2037).epsilon(tol));
4410 CHECK(AS->cpmolar() == Catch::Approx(97.5182).epsilon(tol));
4411 CHECK(AS->speed_sound() == Catch::Approx(149.388).epsilon(tol));
4414 SECTION(
"T=280 K, rho=11000 mol/m3 (compressed liquid)") {
4417 CAPTURE(AS->cvmolar());
4418 CAPTURE(AS->cpmolar());
4419 CAPTURE(AS->speed_sound());
4420 CHECK(AS->p() == Catch::Approx(28.95760e6).epsilon(tol));
4421 CHECK(AS->cvmolar() == Catch::Approx(101.930).epsilon(tol));
4422 CHECK(AS->cpmolar() == Catch::Approx(139.307).epsilon(tol));
4423 CHECK(AS->speed_sound() == Catch::Approx(738.905).epsilon(tol));
4426 SECTION(
"T=280 K, rho=100 mol/m3 (gas)") {
4429 CAPTURE(AS->cvmolar());
4430 CAPTURE(AS->cpmolar());
4431 CAPTURE(AS->speed_sound());
4432 CHECK(AS->p() == Catch::Approx(0.2185345e6).epsilon(tol));
4433 CHECK(AS->cvmolar() == Catch::Approx(91.3497).epsilon(tol));
4434 CHECK(AS->cpmolar() == Catch::Approx(102.623).epsilon(tol));
4435 CHECK(AS->speed_sound() == Catch::Approx(141.882).epsilon(tol));
4438 SECTION(
"T=340 K, rho=8000 mol/m3 (liquid)") {
4441 CAPTURE(AS->cvmolar());
4442 CAPTURE(AS->cpmolar());
4443 CAPTURE(AS->speed_sound());
4444 CHECK(AS->p() == Catch::Approx(2.309798e6).epsilon(tol));
4445 CHECK(AS->cvmolar() == Catch::Approx(113.805).epsilon(tol));
4446 CHECK(AS->cpmolar() == Catch::Approx(195.748).epsilon(tol));
4447 CHECK(AS->speed_sound() == Catch::Approx(265.888).epsilon(tol));
4450 SECTION(
"T=340 K, rho=1000 mol/m3 (superheated vapor)") {
4453 CAPTURE(AS->cvmolar());
4454 CAPTURE(AS->cpmolar());
4455 CAPTURE(AS->speed_sound());
4456 CHECK(AS->p() == Catch::Approx(1.855076e6).epsilon(tol));
4457 CHECK(AS->cvmolar() == Catch::Approx(113.479).epsilon(tol));
4458 CHECK(AS->cpmolar() == Catch::Approx(168.646).epsilon(tol));
4459 CHECK(AS->speed_sound() == Catch::Approx(114.354).epsilon(tol));
4462 SECTION(
"T=368 K, rho=4200 mol/m3 (near-critical)") {
4465 CAPTURE(AS->cvmolar());
4466 CAPTURE(AS->cpmolar());
4467 CAPTURE(AS->speed_sound());
4468 CHECK(AS->p() == Catch::Approx(3.394716e6).epsilon(tol));
4469 CHECK(AS->cvmolar() == Catch::Approx(149.703).epsilon(tol));
4471 CHECK(AS->cpmolar() == Catch::Approx(48981.3).epsilon(5e-3));
4472 CHECK(AS->speed_sound() == Catch::Approx(76.3597).epsilon(tol));
4476TEST_CASE(
"Lemmon-IJT-2022 R1234yf fixed-point constants",
"[R1234yf],[Lemmon-IJT-2022]") {
4478 CHECK(AS->T_critical() == Catch::Approx(367.85).epsilon(1e-5));
4479 CHECK(AS->p_critical() == Catch::Approx(3384400.0).epsilon(1e-4));
4480 CHECK(AS->rhomolar_critical() == Catch::Approx(4180.0).epsilon(1e-4));
4481 CHECK(AS->Ttriple() == Catch::Approx(121.6).epsilon(1e-4));
4497TEST_CASE(
"McLinden-JCED-2020 R1336mzz(Z) fixed-point constants",
"[R1336mzzZ],[McLinden-JCED-2020-R1336mzzZ]") {
4499 CHECK(AS->T_critical() == Catch::Approx(444.5).epsilon(1e-5));
4500 CHECK(AS->p_critical() == Catch::Approx(2.903e6).epsilon(1e-3));
4501 CHECK(AS->rhomolar_critical() == Catch::Approx(3044.0).epsilon(1e-3));
4502 CHECK(AS->molar_mass() == Catch::Approx(0.164056).epsilon(1e-5));
4523TEST_CASE(
"Bell-JPCRD-2022 mixture alphar check values (Table XI)",
"[mixtures],[Bell-JPCRD-2022]") {
4528 const double tol = 1e-10;
4532 SECTION(
"R1234yf + R1234ze(E): Table XI") {
4534 AS->set_mole_fractions({0.4, 0.6});
4537 CAPTURE(AS->alphar());
4538 CHECK(AS->alphar() == Catch::Approx(-0.46467899824257763).epsilon(tol));
4542 SECTION(
"R1234yf + R134a: Table XI") {
4544 AS->set_mole_fractions({0.4, 0.6});
4547 CAPTURE(AS->alphar());
4548 CHECK(AS->alphar() == Catch::Approx(-0.46550859405816197).epsilon(tol));
4551 SECTION(
"R134a + R1234ze(E): Table XI") {
4553 AS->set_mole_fractions({0.4, 0.6});
4556 CAPTURE(AS->alphar());
4557 CHECK(AS->alphar() == Catch::Approx(-0.46245130334193).epsilon(tol));
4562 SECTION(
"R1234yf + R1234ze(E): inverted component order") {
4564 AS->set_mole_fractions({0.6, 0.4});
4567 CAPTURE(AS->alphar());
4568 CHECK(AS->alphar() == Catch::Approx(-0.46467899824257763).epsilon(tol));
4570 SECTION(
"R134a + R1234ze(E): inverted component order") {
4572 AS->set_mole_fractions({0.6, 0.4});
4575 CAPTURE(AS->alphar());
4576 CHECK(AS->alphar() == Catch::Approx(-0.46245130334193).epsilon(tol));
4578 SECTION(
"R1234yf + R134a: inverted component order") {
4580 AS->set_mole_fractions({0.6, 0.4});
4583 CAPTURE(AS->alphar());
4584 CHECK(AS->alphar() == Catch::Approx(-0.46550859405816197).epsilon(tol));
4588TEST_CASE(
"Bell-JPCRD-2023 mixture alphar check values (Table XIII)",
"[mixtures],[Bell-JPCRD-2023]") {
4590 const double tol = 1e-10;
4593 SECTION(
"R32 + R1234yf: Table XIII") {
4595 AS->set_mole_fractions({0.4, 0.6});
4598 CAPTURE(AS->alphar());
4599 CHECK(AS->alphar() == Catch::Approx(-0.47311064743911).epsilon(tol));
4602 SECTION(
"R32 + R1234ze(E): Table XIII") {
4604 AS->set_mole_fractions({0.4, 0.6});
4607 CAPTURE(AS->alphar());
4608 CHECK(AS->alphar() == Catch::Approx(-0.48576186760231).epsilon(tol));
4611 SECTION(
"R125 + R1234yf: Table XIII") {
4613 AS->set_mole_fractions({0.4, 0.6});
4616 CAPTURE(AS->alphar());
4617 CHECK(AS->alphar() == Catch::Approx(-0.46576307479447).epsilon(tol));
4620 SECTION(
"R1234yf + R152a: Table XIII") {
4622 AS->set_mole_fractions({0.4, 0.6});
4625 CAPTURE(AS->alphar());
4626 CHECK(AS->alphar() == Catch::Approx(-0.48967548916638).epsilon(tol));
4629 SECTION(
"R1234ze(E) + R227ea: Table XIII") {
4631 AS->set_mole_fractions({0.4, 0.6});
4634 CAPTURE(AS->alphar());
4635 CHECK(AS->alphar() == Catch::Approx(-0.45378834770736).epsilon(tol));
4657TEST_CASE(
"NIST IR 8570 mixture alphar check values (Tables 4-3 / 4-4)",
"[mixtures],[NIST-IR-8570]") {
4658 const double tol = 1e-10;
4662 SECTION(
"R-1132(E) + R-32: Table 4-3") {
4664 AS->set_mole_fractions({0.4, 0.6});
4667 CAPTURE(AS->alphar());
4668 CHECK(AS->alphar() == Catch::Approx(-0.5172864096181671).epsilon(tol));
4670 SECTION(
"R-1132(E) + R-32: inverted component order") {
4672 AS->set_mole_fractions({0.6, 0.4});
4675 CAPTURE(AS->alphar());
4676 CHECK(AS->alphar() == Catch::Approx(-0.5172864096181671).epsilon(tol));
4681 SECTION(
"R-1132(E) + R-1234yf: Table 4-3") {
4683 AS->set_mole_fractions({0.4, 0.6});
4686 CAPTURE(AS->alphar());
4687 CHECK(AS->alphar() == Catch::Approx(-0.4749927188694013).epsilon(tol));
4689 SECTION(
"R-1132(E) + R-1234yf: inverted component order") {
4691 AS->set_mole_fractions({0.6, 0.4});
4694 CAPTURE(AS->alphar());
4695 CHECK(AS->alphar() == Catch::Approx(-0.4749927188694013).epsilon(tol));
4701 SECTION(
"R-1336mzz(Z) + R-1130(E): Table 4-3 + 4-4") {
4703 AS->set_mole_fractions({0.4, 0.6});
4706 CAPTURE(AS->alphar());
4707 CHECK(AS->alphar() == Catch::Approx(-0.4936442709738808).epsilon(tol));
4709 SECTION(
"R-1336mzz(Z) + R-1130(E): inverted component order") {
4711 AS->set_mole_fractions({0.6, 0.4});
4714 CAPTURE(AS->alphar());
4715 CHECK(AS->alphar() == Catch::Approx(-0.4936442709738808).epsilon(tol));
4719TEST_CASE(
"NIST IR 8570 PropsSI saturation smoke check",
"[mixtures],[NIST-IR-8570]") {
4721 SECTION(
"R-1132(E) + R-32 saturation at 300 K returns finite") {
4724 CHECK(std::isfinite(p));
4727 SECTION(
"R-1132(E) + R-1234yf saturation at 300 K returns finite") {
4728 double p =
CoolProp::PropsSI(
"P",
"T", 300.0,
"Q", 0,
"R1132E[0.5]&R1234yf[0.5]");
4730 CHECK(std::isfinite(p));
4733 SECTION(
"R-1336mzz(Z) + R-1130(E) saturation at 380 K returns finite") {
4735 double p =
CoolProp::PropsSI(
"P",
"T", 380.0,
"Q", 0,
"R1336mzz(Z)[0.5]&R1130(E)[0.5]");
4737 CHECK(std::isfinite(p));
4787TEST_CASE(
"Fluid batch 2020-2024: verify EOS against paper validation tables",
"[fluids][batch_2020_2024]") {
4791 double T_K, rho_molm3;
4792 double p_Pa, cv_JmolK, cp_JmolK, w_ms;
4796 const std::vector<row> rows = {
4798 {
"R1224YDZ", 400.0, 8000.0, 21.17909e6, 139.592, 185.184, 489.479, 1e-5,
"Akasaka & Lemmon, IJT 2023, Table 7 row 4"},
4799 {
"R1132E", 330.0, 12000.0, 3.845082e6, 70.9361, 165.548, 314.193, 1e-5,
"Akasaka & Lemmon, IJT 2024, Table 6 row 4"},
4800 {
"Tetrahydrofuran", 450.0, 10000.0, 12.357974600e6, 0.0, 167.23826646, 739.195761440, 1e-6,
4801 "Fiedler et al., IJT 2023, Table 11 row 3 (cv not published)"},
4802 {
"PropyleneGlycol", 400.0, 13000.0, 61.287909e6, 0.0, 227.48403, 1467.8267, 1e-5,
4803 "Eisenbach et al., JPCRD 2021, Table 8 row 2 (cv not published)"},
4804 {
"VinylChloride", 300.0, 15000.0, 23.0374719e6, 0.0, 91.4066946, 1008.04450, 1e-6,
4805 "Thol, Fenkl & Lemmon, IJT 2022, Table 5 row 4 (cv not published)"},
4806 {
"R1123", 320.0, 11000.0, 5.456590e6, 74.3579, 158.839, 296.996, 1e-5,
"Akasaka et al., IJR 2020, Table 8 row 4"},
4807 {
"n-Perfluorobutane", 360.0, 5200.0, 3.128110e6, 223.0894, 303.2828, 226.8389, 1e-5,
"Gao et al., IECR 2022, Table 14 C4F10 row 3"},
4808 {
"n-Perfluoropentane", 390.0, 4200.0, 1.496384e6, 273.9917, 375.3160, 182.6921, 1e-5,
"Gao et al., IECR 2022, Table 14 C5F12 row 3"},
4809 {
"n-Perfluorohexane", 410.0, 3700.0, 0.9573522e6, 336.7461, 435.6546, 181.2565, 1e-5,
"Gao et al., IECR 2022, Table 14 C6F14 row 3"},
4810 {
"R1233zd(E)", 400.0, 8000.0, 10.79073e6, 122.693, 176.124, 441.123, 1e-5,
4811 "Akasaka & Lemmon, JPCRD 2022, Table IX row 4 (supersedes Mondejar-JCED-2015)"},
4812 {
"R1130(E)", 320.0, 12500.0, 3.39671e6, 76.665, 115.586, 946.434, 1e-5,
4813 "Huber, Kazakov & Lemmon, IJT 2025, Table 4 row 3 (g_i != 1 in exponential terms)"},
4814 {
"R1243zf", 280.0, 11000.0, 7.393335e6, 90.7467, 130.734, 648.467, 1e-5,
4815 "Akasaka & Lemmon, IJT 2025, Table 6 row 2 (3rd EOS, g_i != 1; supersedes Akasaka-JCED-2019)"},
4818 for (
const auto& r : rows) {
4819 SECTION(std::string(r.fluid) +
" (" + r.note +
")") {
4822 CAPTURE(r.rho_molm3);
4824 const double p_calc =
PropsSI(
"P",
"T", r.T_K,
"Dmolar", r.rho_molm3, r.fluid);
4825 const double cp_calc =
PropsSI(
"Cpmolar",
"T", r.T_K,
"Dmolar", r.rho_molm3, r.fluid);
4826 const double w_calc =
PropsSI(
"A",
"T", r.T_K,
"Dmolar", r.rho_molm3, r.fluid);
4828 CHECK(p_calc == Catch::Approx(r.p_Pa).epsilon(r.rtol));
4829 CHECK(cp_calc == Catch::Approx(r.cp_JmolK).epsilon(r.rtol));
4830 CHECK(w_calc == Catch::Approx(r.w_ms).epsilon(r.rtol));
4834 if (r.cv_JmolK != 0.0) {
4835 const double cv_calc =
PropsSI(
"Cvmolar",
"T", r.T_K,
"Dmolar", r.rho_molm3, r.fluid);
4836 CHECK(cv_calc == Catch::Approx(r.cv_JmolK).epsilon(r.rtol));
4842TEST_CASE(
"Qmass output: pure fluid equals Qmolar",
"[Qmass]") {
4844 for (
double Q : {0.0, 0.1, 0.5, 0.9, 1.0}) {
4846 CHECK(AS->Qmass() == Catch::Approx(Q).epsilon(1e-12));
4850TEST_CASE(
"Qmass output: HEOS mixture differs from Qmolar and is internally consistent",
"[Qmass][mixture]") {
4852 AS->set_mole_fractions({0.5, 0.5});
4857 const double MM_R32 = AS_R32->molar_mass();
4858 const double MM_R125 = AS_R125->molar_mass();
4860 for (
double Q : {0.1, 0.3, 0.5, 0.7, 0.9}) {
4863 const auto x = AS->mole_fractions_liquid();
4864 const auto y = AS->mole_fractions_vapor();
4865 const double MM_l =
static_cast<double>(x[0]) * MM_R32 +
static_cast<double>(x[1]) * MM_R125;
4866 const double MM_v =
static_cast<double>(y[0]) * MM_R32 +
static_cast<double>(y[1]) * MM_R125;
4867 const double Qmass_expected = (Q * MM_v) / (Q * MM_v + (1.0 - Q) * MM_l);
4869 CHECK(AS->Qmass() == Catch::Approx(Qmass_expected).epsilon(1e-6));
4871 if (Q > 0.05 && Q < 0.95) {
4872 CHECK(std::abs(AS->Qmass() - Q) > 1e-3);
4877TEST_CASE(
"Qmass input: HEOS R32+R125 round-trip via QmassT_INPUTS",
"[Qmass][mixture]") {
4879 AS->set_mole_fractions({0.5, 0.5});
4881 const double Qmass_observed = AS->Qmass();
4882 const double p_ref = AS->p();
4883 const double Q_ref = AS->Q();
4886 AS2->set_mole_fractions({0.5, 0.5});
4888 CHECK(AS2->p() == Catch::Approx(p_ref).epsilon(1e-8));
4889 CHECK(AS2->Q() == Catch::Approx(Q_ref).epsilon(1e-8));
4890 CHECK(AS2->Qmass() == Catch::Approx(Qmass_observed).epsilon(1e-10));
4893TEST_CASE(
"Qmass input: pure Water round-trips for QmassT and PQmass",
"[Qmass][pure]") {
4897 SECTION(
"QmassT_INPUTS") {
4899 const double Qmass = ref->Qmass();
4900 const double p_ref = ref->p();
4902 CHECK(sut->T() == Catch::Approx(350.0).epsilon(1e-12));
4903 CHECK(sut->p() == Catch::Approx(p_ref).epsilon(1e-12));
4904 CHECK(sut->Q() == Catch::Approx(0.4).epsilon(1e-12));
4907 SECTION(
"PQmass_INPUTS") {
4909 const double p_ref = ref->p();
4910 const double Qmass = ref->Qmass();
4912 CHECK(sut->Q() == Catch::Approx(0.4).epsilon(1e-12));
4913 CHECK(sut->T() == Catch::Approx(350.0).epsilon(1e-12));
4917TEST_CASE(
"Qmass input: HEOS mixture round-trip via PQmass / HmolarQmass / DmolarQmass",
"[Qmass][mixture]") {
4920 AS->set_mole_fractions({0.5, 0.5});
4927 const double Qmass = ref->Qmass();
4928 const double T_ref = ref->T();
4932 CHECK(sut->T() == Catch::Approx(T_ref).epsilon(1e-8));
4933 CHECK(sut->Q() == Catch::Approx(0.4).epsilon(1e-8));
4968TEST_CASE(
"Qmass output: REFPROP R32+R125 matches HEOS to leading digits",
"[Qmass][REFPROP]") {
4969 std::shared_ptr<CoolProp::AbstractState> AS;
4973 WARN(
"REFPROP not available; skipping");
4976 AS->set_mole_fractions({0.5, 0.5});
4978 const double Qmass_refprop = AS->Qmass();
4981 AS_heos->set_mole_fractions({0.5, 0.5});
4983 const double Qmass_heos = AS_heos->Qmass();
4987 CHECK(Qmass_refprop == Catch::Approx(Qmass_heos).epsilon(1e-2));
4989 CHECK(Qmass_refprop > 0.0);
4990 CHECK(Qmass_refprop < 1.0);
4991 CHECK(std::abs(Qmass_refprop - 0.4) > 1e-3);
4994TEST_CASE(
"Qmass input: REFPROP R32+R125 native kq=2 fast path",
"[Qmass][REFPROP]") {
4995 std::shared_ptr<CoolProp::AbstractState> AS;
4999 WARN(
"REFPROP not available; skipping");
5002 AS->set_mole_fractions({0.5, 0.5});
5004 const double Qmass = AS->Qmass();
5005 const double P_ref = AS->p();
5006 const double Q_ref = AS->Q();
5009 AS2->set_mole_fractions({0.5, 0.5});
5011 CHECK(AS2->p() == Catch::Approx(P_ref).epsilon(1e-5));
5012 CHECK(AS2->Q() == Catch::Approx(Q_ref).epsilon(1e-5));
5013 CHECK(AS2->Qmass() == Catch::Approx(Qmass).epsilon(1e-12));
5017 AS3->set_mole_fractions({0.5, 0.5});
5019 CHECK(AS3->T() == Catch::Approx(280.0).epsilon(1e-5));
5020 CHECK(AS3->Q() == Catch::Approx(Q_ref).epsilon(1e-5));
5023TEST_CASE(
"Qmass edge cases: bubble/dew, out-of-range, single-phase",
"[Qmass][edge]") {
5025 AS->set_mole_fractions({0.5, 0.5});
5027 SECTION(
"Qmass = 0 (bubble) bypasses iteration") {
5029 CHECK(AS->Q() == Catch::Approx(0.0).epsilon(1e-12));
5030 CHECK(AS->Qmass() == Catch::Approx(0.0).epsilon(1e-12));
5033 SECTION(
"Qmass = 1 (dew) bypasses iteration") {
5035 CHECK(AS->Q() == Catch::Approx(1.0).epsilon(1e-12));
5036 CHECK(AS->Qmass() == Catch::Approx(1.0).epsilon(1e-12));
5039 SECTION(
"Qmass < 0 throws") {
5043 SECTION(
"Qmass > 1 throws") {
5047 SECTION(
"Qmass() in single-phase state throws") {
5053TEST_CASE(
"Qmass: PropsSI integration (output + input)",
"[Qmass][PropsSI]") {
5054 SECTION(
"Qmass as output for pure Water == Q") {
5055 const double Q = 0.3,
T = 350.0;
5058 CHECK(Qmolar == Catch::Approx(0.3).epsilon(1e-12));
5059 CHECK(Qmass == Catch::Approx(0.3).epsilon(1e-12));
5061 SECTION(
"Qmass as input for pure Water round-trip via PropsSI") {
5062 const double T = 350.0;
5065 CHECK(P_via == Catch::Approx(P_ref).epsilon(1e-12));
5067 SECTION(
"Qmass as input for HEOS R32+R125 mixture via PropsSI") {
5072 AS->set_mole_fractions({0.5, 0.5});
5074 const double Qmass_obs = AS->Qmass();
5075 const double P_ref = AS->p();
5078 AS2->set_mole_fractions({0.5, 0.5});
5080 CHECK(AS2->p() == Catch::Approx(P_ref).epsilon(1e-8));
5084TEST_CASE(
"Qmass: PCSAFT mixture output works after Q-pair flash",
"[Qmass][PCSAFT][mixture]") {
5089 AS->set_mole_fractions({0.0252, 0.9748});
5094 CHECK(AS->Q() == Catch::Approx(0.0).epsilon(1e-10));
5095 CHECK(AS->Qmass() == Catch::Approx(0.0).epsilon(1e-10));
5099 AS2->set_mole_fractions({0.0252, 0.9748});
5101 CHECK(AS2->p() == Catch::Approx(AS->p()).epsilon(1e-10));
5102 CHECK(AS2->Q() == Catch::Approx(0.0).epsilon(1e-10));
5105TEST_CASE(
"Qmass: SRK cubic mixture output works after Q-pair flash",
"[Qmass][cubic][mixture]") {
5109 AS->set_mole_fractions({0.5, 0.5});
5111 CHECK(AS->Q() == Catch::Approx(0.0).epsilon(1e-10));
5112 CHECK(AS->Qmass() == Catch::Approx(0.0).epsilon(1e-10));
5115 AS2->set_mole_fractions({0.5, 0.5});
5117 CHECK(AS2->T() == Catch::Approx(AS->T()).epsilon(1e-10));
5118 CHECK(AS2->Q() == Catch::Approx(0.0).epsilon(1e-10));
5121TEST_CASE(
"User-fluid schema validation rejects malformed PCSAFT/cubic payloads",
"[json_validation]") {
5124 SECTION(
"Cubic SRK - invalid JSON syntax throws") {
5130 SECTION(
"Cubic SRK - schema-invalid payload (missing required fields) throws") {
5133 const std::string bad_cubic = R
"([{"name":"BadFluid","CAS":"000-00-0","Tc":400.0,"pc":3e6,"molemass":0.04}])";
5144 SECTION(
"PCSAFT - invalid JSON syntax throws at debug_level > 0") {
5149 struct DebugLevelGuard
5153 ~DebugLevelGuard() {
5165 SECTION(
"PCSAFT - schema-invalid payload (missing required fields) throws at debug_level > 0") {
5166 struct DebugLevelGuard
5170 ~DebugLevelGuard() {
5181TEST_CASE("Water TS_INPUTS flash near 631-634 K is smooth (no spike to 6e13 Pa)",
"[water_flash][2079]") {
5186 const double s = 6763.617210539725;
5187 const double p_expected = 3.1e6;
5188 for (
double T : {631.0, 632.0, 633.0, 634.0}) {
5192 CHECK(std::isfinite(p));
5195 CHECK(std::abs(p - p_expected) / p_expected < 0.05);
5199TEST_CASE(
"Water HS_INPUTS flash near H=3133800, S=6777 is smooth (no spike to 5.9e13 Pa)",
"[water_flash][1730]") {
5204 for (
double H : {3132000.0, 3133000.0, 3133500.0, 3133800.0, 3134000.0, 3135000.0}) {
5208 CHECK(std::isfinite(p));
5211 CHECK(std::abs(p - 2.97e6) / 2.97e6 < 0.02);
5215TEST_CASE(
"Saturation ancillary returns NaN above T_r instead of UB / SIGFPE (#1611)",
"[ancillary][1611]") {
5226 double Tcrit = AS->T_critical();
5230 CHECK(std::isnan(fluid.ancillaries.pL.evaluate(Tcrit + 5.0)));
5231 CHECK(std::isnan(fluid.ancillaries.pV.evaluate(Tcrit + 5.0)));
5232 CHECK(std::isnan(fluid.ancillaries.rhoL.evaluate(Tcrit + 5.0)));
5233 CHECK(std::isnan(fluid.ancillaries.rhoV.evaluate(Tcrit + 5.0)));
5235 CHECK(std::isfinite(fluid.ancillaries.pL.evaluate(0.95 * Tcrit)));
5236 CHECK(std::isfinite(fluid.ancillaries.pL.evaluate(0.5 * Tcrit)));
5244TEST_CASE(
"INCOMP psat throws below TminPsat instead of returning 0 silently (#2209)",
"[INCOMP][2209]") {
5251 const double v =
CoolProp::PropsSI(
"P",
"Q", 0,
"T", 373.0,
"INCOMP::MEG[0.1]");
5255 CHECK(err.find(
"TminPsat") != std::string::npos);
5257 const double v2 =
CoolProp::PropsSI(
"P",
"Q", 0,
"T", 373.0,
"INCOMP::LiBr[0.1]");
5261TEST_CASE(
"Incompressible enthalpy is finite and continuous at T == Tbase (#1578)",
"[INCOMP][1578]") {
5270 const double p = 101325.0;
5277 const std::vector<Case> cases = {
5278 {
"INCOMP::AS30", 273.15},
5279 {
"INCOMP::TD12", 345.65},
5280 {
"INCOMP::AN-30%", 293.15},
5281 {
"INCOMP::AL-30%", 293.15},
5283 for (
const Case& c : cases) {
5284 const double dT = 0.1;
5285 double h_lo = 0, h_mid = 0, h_hi = 0;
5286 CHECK_NOTHROW(h_lo =
CoolProp::PropsSI(
"Hmass",
"T", c.Tbase - dT,
"P", p, c.name));
5287 CHECK_NOTHROW(h_mid =
CoolProp::PropsSI(
"Hmass",
"T", c.Tbase,
"P", p, c.name));
5288 CHECK_NOTHROW(h_hi =
CoolProp::PropsSI(
"Hmass",
"T", c.Tbase + dT,
"P", p, c.name));
5301 const double midpoint = 0.5 * (h_lo + h_hi);
5302 CHECK(std::abs(h_mid - midpoint) < 1.0);
5305TEST_CASE(
"Incompressible MPG2 viscosity matches Melinder source data (#1374)",
"[INCOMP][1374]") {
5315 const double p = 101325.0;
5316 const double rel = 0.01;
5322 const std::vector<Pt> pts = {
5323 {283.15, 0.42, 742.1231823e-5},
5324 {283.15, 0.52, 1137.598557e-5},
5325 {283.15, 0.57, 1443.949841e-5},
5326 {303.15, 0.42, 326.47355e-5},
5327 {303.15, 0.52, 456.9447475e-5},
5329 for (
const Pt& pt : pts) {
5330 const std::string name =
"INCOMP::MPG2" +
format(
"[%f]", pt.x);
5340 const double Pr =
CoolProp::PropsSI(
"PRANDTL",
"T", 283.15,
"P", p,
"INCOMP::MPG2[0.5]");
5344TEST_CASE(
"PropsSImulti with empty input vectors returns empty result instead of segfaulting",
"[PropsSImulti][2417]") {
5348 std::vector<std::string> outputs{
"T"};
5349 std::vector<std::string> fluids{
"Ammonia"};
5350 std::vector<double> fractions{1.0};
5351 std::vector<double> p_empty;
5352 std::vector<double> q_empty;
5356 std::vector<double> p_one{101325.0};
5357 std::vector<double> q_one{0.0};
5359 REQUIRE(IO2.size() == 1);
5360 REQUIRE(IO2[0].size() == 1);
5361 CHECK(IO2[0][0] > 0);
5363TEST_CASE(
"first_saturation_deriv mixture: dT/dP via Gernert vs finite-difference along bubble curve",
"[first_saturation_deriv][2091]") {
5369 AS->set_mole_fractions({0.7, 0.3});
5377 AS2->set_mole_fractions({0.7, 0.3});
5380 double pP = AS2->p();
5382 double pM = AS2->p();
5383 double dTdP_fd = (2 * dT) / (pP - pM);
5388 CHECK(std::abs(dTdP - dTdP_fd) / std::abs(dTdP_fd) < 5e-3);
5391TEST_CASE(
"first_saturation_deriv mixture: throws at intermediate Q",
"[first_saturation_deriv][2091]") {
5395 AS->set_mole_fractions({0.7, 0.3});
5400TEST_CASE(
"Two-phase chemical_potential mirrors sat-state values; fugacity_coefficient throws",
"[mixtures][2345-followup]") {
5410 AS->set_mole_fractions({0.25, 0.25, 0.5});
5415 REQUIRE(heos_ptr !=
nullptr);
5416 auto& satL = heos_ptr->get_SatL();
5417 auto& satV = heos_ptr->get_SatV();
5419 for (std::size_t i = 0; i < 3; ++i) {
5422 const double mu = AS->chemical_potential(i);
5423 const double muL = satL.chemical_potential(i);
5424 const double muV = satV.chemical_potential(i);
5425 CHECK(std::isfinite(mu));
5427 CHECK(std::abs(muL - muV) < 1e-4 * std::abs(muL));
5429 CHECK(std::abs(mu - 0.5 * (muL + muV)) < 1e-9 * std::abs(0.5 * (muL + muV)));
5432 CHECK_THROWS(AS->fugacity_coefficient(i));
5434 CHECK(std::isfinite(satL.fugacity_coefficient(i)));
5435 CHECK(std::isfinite(satV.fugacity_coefficient(i)));
5439TEST_CASE(
"HAPropsSI Twb at high T (T > Tsat) returns the physical root (#2255)",
"[HAPropsSI][2255]") {
5446 const double Twb_C = Twb_K - 273.15;
5449 CHECK(Twb_C > 125.0);
5450 CHECK(Twb_C < 135.0);
5455 for (
double T_C : {100.0, 105.0, 110.0, 115.0, 120.0, 130.0, 150.0, 200.0, 250.0, 300.0}) {
5456 const double Twb =
HumidAir::HAPropsSI(
"Twb",
"T", T_C + 273.15,
"W", 0.05,
"P", 101325.0) - 273.15;
5459 CHECK(std::isfinite(Twb));
5464 CHECK(Twb >= prev - 1e-6);
5469TEST_CASE(
"HAPropsSI T_db from (T_wb, RelHum, P) — issue #2690 surviving failure modes",
"[HAPropsSI][humid_air][2690]") {
5477 SECTION(
"Mode B — Tsat(p) crossing isolated outliers") {
5482 const double T_db_a =
HumidAir::HAPropsSI(
"T_db",
"T_wb", 30 + 273.15,
"RelHum", 1e-5,
"P", 97950.0);
5484 CHECK(std::isfinite(T_db_a));
5485 CHECK(T_db_a > 30 + 273.15);
5486 CHECK(T_db_a < 200 + 273.15);
5488 const double T_db_b =
HumidAir::HAPropsSI(
"T_db",
"T_wb", 28 + 273.15,
"RelHum", 1e-4,
"P", 87550.0);
5490 CHECK(std::isfinite(T_db_b));
5491 CHECK(T_db_b > 28 + 273.15);
5494 SECTION(
"Mode A — pressure-band cases incidentally fixed by b3360b8c6 stay green") {
5498 for (
double P : {90200.0, 90550.0, 90700.0, 91000.0, 91100.0, 91024.0}) {
5499 const double T_db =
HumidAir::HAPropsSI(
"T_db",
"T_wb", 30 + 273.15,
"RelHum", 1e-5,
"P", P);
5502 CHECK(std::isfinite(T_db));
5503 CHECK(T_db > 30 + 273.15);
5504 CHECK(T_db < 200 + 273.15);
5508 SECTION(
"Mode C — physically infeasible inputs surface a clear error, not 'inf'") {
5519 const double r1 =
HumidAir::HAPropsSI(
"T_db",
"T_wb", 60 + 273.15,
"RelHum", 1e-5,
"P", 90000.0);
5523 CHECK(err1.find(
"beyond the validity range") != std::string::npos);
5525 const double r2 =
HumidAir::HAPropsSI(
"T_db",
"T_wb", 65 + 273.15,
"RelHum", 1e-4,
"P", 95000.0);
5529 CHECK(err2.find(
"beyond the validity range") != std::string::npos);
5532 SECTION(
"Reporter sweep (td.xlsx) — Modes B+C clean, Mode D out of scope") {
5540 int unexpected_failures_above_freezing = 0;
5541 int mode_c_above_freezing = 0;
5542 int fail_at_freezing = 0;
5543 const double T_wbs[] = {0, 5, 10, 15, 20, 25, 28, 29, 30, 31, 32, 35, 40, 45, 50, 55, 60, 65};
5544 const double RHs[] = {0.01, 1e-3, 1e-4, 1e-5};
5545 for (
double RH : RHs) {
5546 for (
double T_wb_C : T_wbs) {
5547 for (
int P = 87000; P < 99000; P += 250) {
5548 const double v =
HumidAir::HAPropsSI(
"T_db",
"T_wb", T_wb_C + 273.15,
"RelHum", RH,
"P", (
double)P);
5554 if (err.find(
"beyond the validity range") != std::string::npos) {
5555 ++mode_c_above_freezing;
5557 ++unexpected_failures_above_freezing;
5564 CAPTURE(unexpected_failures_above_freezing);
5565 CAPTURE(mode_c_above_freezing);
5566 CAPTURE(fail_at_freezing);
5568 CHECK(unexpected_failures_above_freezing == 0);
5571 CHECK(mode_c_above_freezing > 0);
5573 CHECK(fail_at_freezing > 0);
5576 SECTION(
"Regression guard — #2697 high-pressure Twb cases must still work") {
5582 CHECK(std::isfinite(Twb));
5583 CHECK(Twb > 100 + 273.15);
5584 CHECK(Twb < 250 + 273.15);
5588TEST_CASE(
"HAPropsSI T_db from T_wb near the water triple point — issue #2906",
"[HAPropsSI][humid_air][2906]") {
5601 SECTION(
"No silently-wrong result: every finite T_db reproduces the requested T_wb") {
5609 int valid = 0, errored_clean = 0, bad = 0;
5610 for (
double RH : {0.01, 0.1, 0.5}) {
5611 for (
int i = 0; i <= 42; ++i) {
5612 const double twb_C = -0.6 + 0.05 * i;
5613 for (
int P = 20000; P <= 98000; P += 13000) {
5614 const double tdb =
HumidAir::HAPropsSI(
"T_db",
"T_wb", twb_C + 273.15,
"RelHum", RH,
"P", (
double)P);
5616 const double back =
HumidAir::HAPropsSI(
"Twb",
"T", tdb,
"RelHum", RH,
"P", (
double)P) - 273.15;
5617 if (
ValidNumber(back) && std::abs(back - twb_C) <= 1e-2)
5623 if (err.find(
"triple point") != std::string::npos)
5632 CAPTURE(errored_clean);
5636 CHECK(errored_clean > 0);
5639 SECTION(
"Reachable wet-bulbs on both branches still solve") {
5642 for (
double tw : {-5.0, -0.5, 1.0, 5.0}) {
5643 const double tdb =
HumidAir::HAPropsSI(
"T_db",
"T_wb", tw + 273.15,
"RelHum", 0.5,
"P", 101325.0);
5645 CHECK(std::isfinite(tdb));
5646 CHECK(tdb >= tw + 273.15 - 1e-6);
5650 SECTION(
"Canonical T_wb = 0 C failing pressures error cleanly, never opaque 'inf'") {
5654 int clean = 0, opaque = 0, unexpected = 0;
5655 for (
int P = 87000; P < 99000; P += 50) {
5656 const double v =
HumidAir::HAPropsSI(
"T_db",
"T_wb", 273.15,
"RelHum", 0.01,
"P", (
double)P);
5661 if (err.find(
"triple point") != std::string::npos)
5663 else if (err.find(
"(inf)") != std::string::npos)
5670 CAPTURE(unexpected);
5673 CHECK(unexpected == 0);
5677TEST_CASE(
"Out-of-range Q in update() does not corrupt cached state (#2195)",
"[update][2195]") {
5687 const double h_legal = AS->hmass();
5688 CHECK(std::isfinite(h_legal));
5689 CHECK(h_legal > 1e6);
5695 const double h_after = AS->hmass();
5697 CHECK((!std::isfinite(h_after) || h_after == h_legal));
5703TEST_CASE(
"Water below 0 C at atmospheric pressure is not silently labelled liquid (#1098)",
"[water_phase][1098]") {
5710 const std::string phase =
CoolProp::PhaseSI(
"T", 273.15 - 5,
"P", 101325.0,
"Water");
5712 CHECK(phase !=
"liquid");
5713 CHECK(phase !=
"twophase");
5714 CHECK(phase !=
"gas");
5717 const double v =
CoolProp::PropsSI(
"Phase",
"T", 273.15 - 5,
"P", 101325.0,
"Water");
5721TEST_CASE(
"DmassSmass round-trip on the dew curve preserves enthalpy (#1907)",
"[water_flash][1907]") {
5736 for (
double d : {200.0, 175.0, 130.0, 70.0, 30.0, 15.126, 11.141, 5.0}) {
5739 const double T_dew = AS->T();
5740 const double h_dew = AS->hmass();
5741 const double s_dew = AS->smass();
5742 const double p_dew = AS->p();
5749 CAPTURE(AS2->hmass());
5750 CAPTURE(AS2->smass());
5751 CHECK(std::abs(AS2->T() - T_dew) / T_dew < 1e-6);
5752 CHECK(std::abs(AS2->hmass() - h_dew) / h_dew < 1e-3);
5753 CHECK(std::abs(AS2->smass() - s_dew) / s_dew < 1e-6);
5754 CHECK(std::abs(AS2->p() - p_dew) / p_dew < 1e-3);
5758TEST_CASE(
"D+{H,S,U} round-trip for sub-triple compressed-liquid water (CoolProp-lgk)",
"[water_flash][HSU_D_subtriple][lgk]") {
5768 const double Tt =
Props1SI(
"Water",
"Ttriple");
5771 CAPTURE(
static_cast<int>(pair));
5773 for (
double p : {100e6, 150e6, 200e6}) {
5775 CAPTURE(p, Tmelt, Tt);
5778 REQUIRE(Tmelt + 1.0 < Tt - 0.5);
5780 for (
double T :
linspace(Tmelt + 1.0, Tt - 0.5, 6)) {
5783 const double d = ref->rhomass();
5787 other = ref->hmass();
5790 other = ref->smass();
5793 other = ref->umass();
5797 REQUIRE_NOTHROW(rt->update(pair, d, other));
5798 CHECK(rt->rhomass() == Catch::Approx(d).epsilon(1e-5));
5799 CHECK(rt->T() == Catch::Approx(
T).epsilon(1e-4));
5808 const double p = 208.5e6;
5810 const double T = Tmelt + 0.5;
5813 const double d = ref->rhomass();
5817 CAPTURE(p, Tmelt,
T, d, other);
5818 REQUIRE_NOTHROW(rt->update(pair, d, other));
5819 CHECK(rt->rhomass() == Catch::Approx(d).epsilon(1e-5));
5820 CHECK(rt->T() == Catch::Approx(
T).epsilon(1e-4));
5824TEST_CASE(
"REFPROP DmolarSmolar honours imposed phase (#2042)",
"[REFPROP][2042]") {
5830 auto AS = std::shared_ptr<CoolProp::AbstractState>(
5832 AS->set_mole_fractions({0.30241, 0.03639, 0.02119, 0.007, 0.0031, 0.0039, 0.0015, 0.0008, 0.0025, 0.0002, 0.62101});
5834 const double rho_in = 405.4197781472575;
5835 const double s_in = 2004.9031527899563;
5838 CHECK(std::abs(AS->rhomass() - rho_in) / rho_in < 1e-6);
5843TEST_CASE(
"change_EOS rejects unknown EOS name",
"[change_EOS][1703]") {
5848 CHECK_THROWS(AS->change_EOS(0,
"kdsfakhds"));
5849 CHECK_THROWS(AS->change_EOS(0,
""));
5850 CHECK_THROWS(AS->change_EOS(99,
"SRK"));
5851 CHECK_NOTHROW(AS->change_EOS(0,
"SRK"));
5852 CHECK_NOTHROW(AS->change_EOS(0,
"Peng-Robinson"));
5853 CHECK_NOTHROW(AS->change_EOS(0,
"XiangDeiters"));
5856TEST_CASE(
"Ammonia d(U)/d(P)|sigma at P=60110.77... is finite (#2244)",
"[ammonia][2244]") {
5861 const double P = 60110.7723310773;
5862 for (
double dP : {-0.001, 0.0, 0.001}) {
5864 const double v =
CoolProp::PropsSI(
"d(U)/d(P)|sigma",
"P", P + dP,
"Q", 0,
"Ammonia");
5866 CHECK(std::isfinite(v));
5872TEST_CASE(
"Ammonia (R717) PT_INPUTS in superheated vapor returns valid enthalpy (#2461)",
"[ammonia][R717][2461]") {
5877 const double P_low = 1.3e5;
5879 const double T_evap = T_sat + 2.0;
5884 CHECK(std::isfinite(h));
5896TEST_CASE(
"DmolarQ branch selection via guess.T",
"[2773][branch_selection]") {
5903 const double rho_near_peak = AS->rhomolar();
5905 const double rho_near_triple = AS->rhomolar();
5906 REQUIRE(rho_near_peak > rho_near_triple);
5907 const double rho_target = 0.5 * (rho_near_peak + rho_near_triple);
5909 REQUIRE(AS->rhomolar() < rho_target);
5911 const double T_density_max = 277.13;
5913 SECTION(
"Guess near low-T (rising) branch → root below density max") {
5917 CHECK(AS->T() < T_density_max);
5918 CHECK(AS->rhomolar() == Catch::Approx(rho_target).epsilon(1e-6));
5920 SECTION(
"Guess near high-T (falling) branch → root above density max") {
5924 CHECK(AS->T() > T_density_max);
5925 CHECK(AS->rhomolar() == Catch::Approx(rho_target).epsilon(1e-6));
5929TEST_CASE(
"HmolarQ branch selection via guess.T (water saturated vapor)",
"[2773][branch_selection]") {
5935 const double T_low_anchor = 470.0;
5937 const double h_target = AS->hmolar();
5938 const double T_h_max_approx = 540.0;
5940 SECTION(
"Guess near T_low_anchor → low-T root") {
5944 CHECK(AS->T() < T_h_max_approx);
5945 CHECK(AS->T() == Catch::Approx(T_low_anchor).epsilon(0.01));
5947 SECTION(
"Guess far above the h-peak → high-T root") {
5951 CHECK(AS->T() > T_h_max_approx);
5952 CHECK(AS->T() != Catch::Approx(T_low_anchor).epsilon(0.05));
5956TEST_CASE(
"Default update() throws MultipleSolutionsError on ambiguous saturation flash",
"[2773][strict_mode]") {
5962 SECTION(
"Water DmolarQ in the rho_L-non-monotonic region near 4 °C") {
5965 const double rho_near_peak = AS->rhomolar();
5967 const double rho_near_triple = AS->rhomolar();
5968 const double rho_target = 0.5 * (rho_near_peak + rho_near_triple);
5973 SECTION(
"Water HmolarQ in the h_g-non-monotonic region around 540 K peak") {
5976 const double h_target = AS->hmolar();
5980 SECTION(
"Single-root DQ still succeeds via the default path") {
5984 const double rho_400 = AS->rhomolar();
5986 CHECK(AS->T() == Catch::Approx(400.0).epsilon(1e-6));
5990TEST_CASE(
"QSmolar branch selection via guess.T (water saturated vapor)",
"[2773][branch_selection]") {
5996 const double T_anchor = 500.0;
5998 const double s_target = AS->smolar();
6002 CHECK(AS->T() == Catch::Approx(T_anchor).epsilon(0.01));
6006TEST_CASE(
"Saturation branch flash: edge cases (#2773)",
"[2773][edge_cases]") {
6008 SECTION(
"Mass-input dispatch: HmassQ_INPUTS converts and resolves") {
6010 const double T_anchor = 470.0;
6012 const double h_mass = AS->hmass();
6016 CHECK(AS->T() == Catch::Approx(T_anchor).epsilon(0.01));
6019 SECTION(
"Mass-input dispatch: QSmass_INPUTS converts and resolves") {
6021 const double T_anchor = 500.0;
6023 const double s_mass = AS->smass();
6027 CHECK(AS->T() == Catch::Approx(T_anchor).epsilon(0.01));
6030 SECTION(
"Reference-state change works correctly across instances") {
6041 struct WaterRefStateGuard
6043 ~WaterRefStateGuard() {
6049 const double T_anchor = 470.0;
6051 const double h_def = AS->hmolar();
6061 const double h_nbp = AS2->hmolar();
6062 REQUIRE(std::abs(h_nbp - h_def) > 100.0);
6067 CHECK(AS2->T() == Catch::Approx(T_anchor).epsilon(0.01));
6070 SECTION(
"Mixture: NotImplementedError preserved") {
6072 std::vector<double> z = {0.5, 0.5};
6073 AS->set_mole_fractions(z);
6079 SECTION(
"guess.T outside saturation range: clear SolutionError") {
6082 const double h_target = AS->hmolar();
6088 SECTION(
"Multi-root region throws MultipleSolutionsError on default update()") {
6094 const double h_low = AS->hmolar();
6098 SECTION(
"Mass-input multi-root region also throws MultipleSolutionsError") {
6103 const double h_low_mass = AS->hmass();
6112TEST_CASE(
"Caloric superancillary is reference-state-agnostic (no thrashing)",
"[2773][ref_state_shared]") {
6115 struct WaterRefStateGuard
6117 WaterRefStateGuard() {
6120 ~WaterRefStateGuard() {
6127 const double T_anchor = 470.0;
6129 const double h_def_anchor = AS_def->hmolar();
6133 CHECK(AS_def->T() == Catch::Approx(T_anchor).epsilon(1e-6));
6135 const auto build_count_after_first =
static_cast<unsigned int>(AS_def->get_fluid_parameter_double(0,
"SUPERANC::caloric_build_count"));
6136 REQUIRE(build_count_after_first >= 1u);
6143 const double h_nbp_anchor = AS_nbp->hmolar();
6144 REQUIRE(std::abs(h_nbp_anchor - h_def_anchor) > 100.0);
6148 CHECK(AS_nbp->T() == Catch::Approx(T_anchor).epsilon(1e-6));
6150 CHECK(AS_def->T() == Catch::Approx(T_anchor).epsilon(1e-6));
6154 for (
int i = 0; i < 5; ++i) {
6156 CHECK(AS_def->T() == Catch::Approx(T_anchor).epsilon(1e-6));
6158 CHECK(AS_nbp->T() == Catch::Approx(T_anchor).epsilon(1e-6));
6163 const auto build_count_final =
static_cast<unsigned int>(AS_def->get_fluid_parameter_double(0,
"SUPERANC::caloric_build_count"));
6164 CHECK(build_count_final == build_count_after_first);
6168 const double s_def_anchor = AS_def->smolar();
6170 const double s_nbp_anchor = AS_nbp->smolar();
6171 REQUIRE(std::abs(s_nbp_anchor - s_def_anchor) > 1.0);
6174 CHECK(AS_def->T() == Catch::Approx(500.0).epsilon(1e-3));
6176 CHECK(AS_nbp->T() == Catch::Approx(500.0).epsilon(1e-3));
6186TEST_CASE(
"Caloric superancillary thread-safe lazy build (#2773)",
"[2773][thread_safety]") {
6190 const auto build_count_before =
static_cast<unsigned int>(probe->get_fluid_parameter_double(0,
"SUPERANC::caloric_build_count"));
6193 constexpr int N_THREADS = 8;
6194 constexpr int N_ITERS_PER_THREAD = 50;
6195 std::atomic<int> error_count{0};
6196 std::atomic<int> success_count{0};
6198 auto worker = [&]() {
6201 const double T_anchor = 470.0;
6203 const double h_target = AS->hmolar();
6206 for (
int i = 0; i < N_ITERS_PER_THREAD; ++i) {
6208 if (std::abs(AS->T() - T_anchor) > 1e-3) {
6209 error_count.fetch_add(1, std::memory_order_relaxed);
6213 success_count.fetch_add(1, std::memory_order_relaxed);
6215 error_count.fetch_add(1, std::memory_order_relaxed);
6219 std::vector<std::thread> threads;
6220 threads.reserve(N_THREADS);
6221 for (
int t = 0; t < N_THREADS; ++t) {
6222 threads.emplace_back(worker);
6224 for (
auto& t : threads) {
6228 CHECK(error_count.load() == 0);
6229 CHECK(success_count.load() == N_THREADS);
6235 const auto build_count_after =
static_cast<unsigned int>(probe2->get_fluid_parameter_double(0,
"SUPERANC::caloric_build_count"));
6236 CHECK(build_count_after - build_count_before <= 1u);
6248TEST_CASE(
"DQ/HQ/QS flash benchmarks (#2773)",
"[2773][bench][!benchmark]") {
6255 const double rho_DQ = AS->rhomolar();
6261 const double h_HQ = AS->hmolar();
6268 const double s_QS = AS_prop->smolar();
6272 BENCHMARK(
"DQ default update(DmolarQ,rho,Q=0)") {
6275 BENCHMARK(
"DQ with guesses update_with_guesses(DmolarQ,rho,Q=0,T)") {
6280 BENCHMARK(
"HQ with guesses update_with_guesses(HmolarQ,h,Q=1,T)") {
6283 BENCHMARK(
"QS with guesses update_with_guesses(QSmolar,Q=0,s,T) [propane]") {
6288TEST_CASE(
"INCOMP backend rejects molar property requests with a clean error",
"[INCOMP][1908]") {
6295 double v =
CoolProp::PropsSI(
"Dmolar",
"T", 298.15,
"P", 101325.0,
"INCOMP::AEG[0.1]");
6299 CHECK(err.find(
"INCOMP") != std::string::npos);
6301 double rhomass =
CoolProp::PropsSI(
"Dmass",
"T", 298.15,
"P", 101325.0,
"INCOMP::AEG[0.1]");
6303 CHECK(rhomass < 2000);
6306TEST_CASE(
"mole_fractions_liquid/vapor reject single-phase states (#2308)",
"[mole_fractions][2308]") {
6312 AS->set_mole_fractions({0.10, 0.34, 0.41, 0.15});
6315 AS->build_phase_envelope(
"");
6326 REQUIRE_NOTHROW(AS->mole_fractions_liquid());
6327 REQUIRE_NOTHROW(AS->mole_fractions_vapor());
6328 auto x = AS->mole_fractions_liquid();
6329 auto y = AS->mole_fractions_vapor();
6330 REQUIRE(x.size() == 4);
6331 REQUIRE(y.size() == 4);
6333 double sx = 0.0, sy = 0.0;
6338 CHECK(sx == Catch::Approx(1.0).epsilon(1e-9));
6339 CHECK(sy == Catch::Approx(1.0).epsilon(1e-9));
6342TEST_CASE(
"REFPROP supports DmolarQ / DmassQ inputs (#1845)",
"[REFPROP][1845]") {
6348 const double T_refprop = AS->T();
6349 CHECK(AS->rhomass() == Catch::Approx(15.0).epsilon(1e-9));
6350 CHECK(T_refprop > 200.0);
6351 CHECK(T_refprop < 250.0);
6355 CHECK(AS->T() == Catch::Approx(AS2->T()).epsilon(1e-6));
6358TEST_CASE(
"TABULAR_NX/NY config keys exist and default to 200",
"[Configuration][TABULAR]") {
6365TEST_CASE(
"BICUBIC PT below saturation no longer segfaults (#1950)",
"[BICUBIC][1950]") {
6380 const double rho = BICU->rhomass();
6381 CHECK(std::isfinite(rho));
6388 CHECK(rho == Catch::Approx(HEOS->rhomass()).epsilon(1e-2));
6391TEST_CASE(
"REFPROP update_DmolarT_direct matches update(DmolarT)",
"[REFPROPsat]") {
6393 std::shared_ptr<AbstractState> direct(AbstractState::factory(
"REFPROP",
"Propane"));
6394 std::shared_ptr<AbstractState> flash(AbstractState::factory(
"REFPROP",
"Propane"));
6395 double T = 300.0, rhomolar = 12000.0;
6398 REQUIRE(be !=
nullptr);
6399 be->update_DmolarT_direct(rhomolar,
T);
6400 CHECK(direct->p() == Catch::Approx(flash->p()).epsilon(1e-9));
6401 CHECK(direct->hmolar() == Catch::Approx(flash->hmolar()).epsilon(1e-9));
6402 CHECK(direct->smolar() == Catch::Approx(flash->smolar()).epsilon(1e-9));
6405TEST_CASE(
"REFPROP saturation shim reproduces saturated densities",
"[REFPROPsat]") {
6407 std::shared_ptr<AbstractState> host(AbstractState::factory(
"REFPROP",
"Propane"));
6410 REQUIRE(be !=
nullptr);
6411 auto shimL = be->build_saturation_shim(0);
6412 auto shimV = be->build_saturation_shim(1);
6413 CHECK(shimL->rhomolar() == Catch::Approx(be->saturated_liquid_keyed_output(
iDmolar)).epsilon(1e-10));
6414 CHECK(shimV->rhomolar() == Catch::Approx(be->saturated_vapor_keyed_output(
iDmolar)).epsilon(1e-10));
6417 std::shared_ptr<AbstractState> probe(AbstractState::factory(
"REFPROP",
"Propane"));
6420 CHECK(shimL->hmolar() == Catch::Approx(probe->hmolar()).epsilon(1e-9));
6423TEST_CASE(
"REFPROP saturated keyed outputs (h,s,cp,visc) match endpoint flashes",
"[REFPROPsat]") {
6425 std::shared_ptr<AbstractState> twophase(AbstractState::factory(
"REFPROP",
"Propane"));
6426 twophase->update(
QT_INPUTS, 0.5, 300.0);
6427 std::shared_ptr<AbstractState> bubble(AbstractState::factory(
"REFPROP",
"Propane"));
6429 std::shared_ptr<AbstractState> dew(AbstractState::factory(
"REFPROP",
"Propane"));
6432 CHECK(twophase->saturated_liquid_keyed_output(
iHmolar) == Catch::Approx(bubble->hmolar()).epsilon(1e-7));
6433 CHECK(twophase->saturated_vapor_keyed_output(
iHmolar) == Catch::Approx(dew->hmolar()).epsilon(1e-7));
6434 CHECK(twophase->saturated_liquid_keyed_output(
iSmolar) == Catch::Approx(bubble->smolar()).epsilon(1e-7));
6435 CHECK(twophase->saturated_liquid_keyed_output(
iCpmolar) == Catch::Approx(bubble->cpmolar()).epsilon(1e-6));
6436 CHECK(twophase->saturated_vapor_keyed_output(
iviscosity) == Catch::Approx(dew->viscosity()).epsilon(1e-6));
6437 CHECK(twophase->saturated_liquid_keyed_output(
iconductivity) == Catch::Approx(bubble->conductivity()).epsilon(1e-6));
6440TEST_CASE(
"REFPROP first_saturation_deriv matches HEOS for a pure fluid",
"[REFPROPsat]") {
6442 std::shared_ptr<AbstractState> RP(AbstractState::factory(
"REFPROP",
"Propane"));
6443 std::shared_ptr<AbstractState> HE(AbstractState::factory(
"HEOS",
"Propane"));
6444 for (
int Ti = 200; Ti <= 360; Ti += 40) {
6448 CHECK(RP->first_saturation_deriv(
iT,
iP) == Catch::Approx(HE->first_saturation_deriv(
iT,
iP)).epsilon(1e-4));
6449 CHECK(RP->first_saturation_deriv(
iDmolar,
iT) == Catch::Approx(HE->first_saturation_deriv(
iDmolar,
iT)).epsilon(1e-3));
6450 CHECK(RP->first_saturation_deriv(
iHmolar,
iP) == Catch::Approx(HE->first_saturation_deriv(
iHmolar,
iP)).epsilon(1e-3));
6455 CHECK(RP->first_saturation_deriv(
iT,
iP) == Catch::Approx(HE->first_saturation_deriv(
iT,
iP)).epsilon(1e-4));
6458TEST_CASE(
"REFPROP first_two_phase_deriv matches HEOS for a pure fluid",
"[REFPROPsat]") {
6460 std::shared_ptr<AbstractState> RP(AbstractState::factory(
"REFPROP",
"Propane"));
6461 std::shared_ptr<AbstractState> HE(AbstractState::factory(
"HEOS",
"Propane"));
6468TEST_CASE(
"REFPROP saturated keyed output throws in single phase",
"[REFPROPsat]") {
6470 std::shared_ptr<AbstractState> AS(AbstractState::factory(
"REFPROP",
"Propane"));
6472 CHECK_THROWS(AS->saturated_liquid_keyed_output(
iHmolar));
6478TEST_CASE(
"REFPROP first_two_phase_deriv_splined matches HEOS (pure)",
"[REFPROPsat]") {
6480 std::shared_ptr<AbstractState> RP(AbstractState::factory(
"REFPROP",
"Propane"));
6481 std::shared_ptr<AbstractState> HE(AbstractState::factory(
"HEOS",
"Propane"));
6486 == Catch::Approx(HE->first_two_phase_deriv_splined(
iDmolar,
iHmolar,
iP, x_end)).epsilon(1e-3));
6488 == Catch::Approx(HE->first_two_phase_deriv_splined(
iDmolar,
iP,
iHmolar, x_end)).epsilon(1e-3));
6491TEST_CASE(
"REFPROP saturation derivs work for a mixture",
"[REFPROPsat]") {
6493 std::shared_ptr<AbstractState> RP(AbstractState::factory(
"REFPROP",
"Methane&Ethane"));
6494 std::vector<double> z = {0.6, 0.4};
6495 RP->set_mole_fractions(z);
6498 double dTdp = RP->first_saturation_deriv(
iT,
iP);
6505 CHECK_THROWS(RP->first_two_phase_deriv_splined(
iDmolar,
iHmolar,
iP, 0.5));
6508TEST_CASE(
"REFPROP set_binary_interaction_string model param is length-guarded (CoolProp-tw7t)",
"[REFPROP][refprop][binary_interaction]") {
6510 std::shared_ptr<AbstractState> RP(AbstractState::factory(
"REFPROP",
"Methane&Ethane"));
6511 RP->set_mole_fractions({0.6, 0.4});
6519 CHECK_THROWS_AS(RP->set_binary_interaction_string(0, 1,
"model",
"ABCD"),
CoolProp::ValueError);
6520 CHECK_THROWS_AS(RP->set_binary_interaction_string(0, 1,
"model",
"TOOLONG"),
CoolProp::ValueError);
6530 RP->set_binary_interaction_string(0, 1,
"model",
"KW0");
6543 std::string m = RP->get_binary_interaction_string(
"74-82-8",
"74-84-0",
"model");
6544 CHECK(m.size() <= 3);
6550TEST_CASE(
"REFPROP cross-check: sat-state fugacity_coefficient agrees with HEOS for Methane/Ethane/Propane",
"[REFPROPsat][2345-followup]") {
6552 const std::vector<double> z = {0.25, 0.25, 0.5};
6554 std::shared_ptr<AbstractState> HEOS(AbstractState::factory(
"HEOS",
"Methane&Ethane&Propane"));
6555 HEOS->set_mole_fractions(z);
6558 std::shared_ptr<AbstractState> RP(AbstractState::factory(
"REFPROP",
"Methane&Ethane&Propane"));
6559 RP->set_mole_fractions(z);
6564 CHECK(std::abs(HEOS->T() - RP->T()) < 0.05);
6567 REQUIRE(heos_mix_ptr !=
nullptr);
6568 auto& satL_heos = heos_mix_ptr->get_SatL();
6569 auto& satV_heos = heos_mix_ptr->get_SatV();
6575 std::shared_ptr<AbstractState> satL_rp(AbstractState::factory(
"REFPROP",
"Methane&Ethane&Propane"));
6576 satL_rp->set_mole_fractions(RP->mole_fractions_liquid());
6577 satL_rp->update(
PQ_INPUTS, RP->p(), 0.0);
6578 std::shared_ptr<AbstractState> satV_rp(AbstractState::factory(
"REFPROP",
"Methane&Ethane&Propane"));
6579 satV_rp->set_mole_fractions(RP->mole_fractions_vapor());
6580 satV_rp->update(
PQ_INPUTS, RP->p(), 1.0);
6582 for (std::size_t i = 0; i < 3; ++i) {
6584 const double phiL_heos = satL_heos.fugacity_coefficient(i);
6585 const double phiL_rp = satL_rp->fugacity_coefficient(i);
6586 const double phiV_heos = satV_heos.fugacity_coefficient(i);
6587 const double phiV_rp = satV_rp->fugacity_coefficient(i);
6594 CHECK(std::abs(phiL_heos - phiL_rp) / std::abs(phiL_rp) < 1e-2);
6595 CHECK(std::abs(phiV_heos - phiV_rp) / std::abs(phiV_rp) < 1e-2);
6604TEST_CASE(
"Surface tension of water is nonzero and matches IAPWS",
"[surface_tension]") {
6610 CHECK(st_300 > 0.0);
6611 CHECK(st_350 > 0.0);
6613 CHECK(st_300 == Catch::Approx(0.07170).epsilon(0.01));
6614 CHECK(st_350 == Catch::Approx(0.06301).epsilon(0.01));
6616 CHECK(st_350 < st_300);