1#if defined(ENABLE_CATCH)
5# include "../Backends/Cubics/CubicBackend.h"
6# include "../Backends/Helmholtz/HelmholtzEOSMixtureBackend.h"
12# include <catch2/catch_all.hpp>
28double equilibrium_residual(
const std::string& backend,
const std::string& fluids,
const std::vector<double>& x,
const std::vector<double>& y,
30 auto L = std::shared_ptr<AbstractState>(AbstractState::factory(backend, fluids));
31 L->set_mole_fractions(x);
34 auto V = std::shared_ptr<AbstractState>(AbstractState::factory(backend, fluids));
35 V->set_mole_fractions(y);
39 for (std::size_t i = 0; i < x.size(); ++i) {
40 if (std::min(x[i], y[i]) < 1e-4)
continue;
41 double fL = L->fugacity(i), fV = V->fugacity(i);
42 if (fL > 1e-15 && fV > 1e-15) maxresid = std::max(maxresid, std::abs(std::log(fV / fL)));
48TEST_CASE(
"Michelsen Flash: Issue #2333 (PR mixture 264.65 K, 6.51 MPa)",
"[michelsen][cubic][flash][2333]") {
50 std::vector<double> z = {0.97, 0.03};
51 AS->set_mole_fractions(z);
58 CHECK(AS->rhomolar() > 0);
61TEST_CASE(
"Michelsen Flash: Issue #1668 (PR mixture high pressure)",
"[michelsen][cubic][flash][1668]") {
63 std::vector<double> z = {0.5, 0.5};
64 AS->set_mole_fractions(z);
68 CHECK(AS->rhomolar() > 0);
71TEST_CASE(
"Michelsen Flash: Issue #2637 (HEOS mixture phase envelope)",
"[michelsen][phase_envelope][2637]") {
76 std::vector<double> z = {0.85, 0.15};
77 AS->set_mole_fractions(z);
79 CHECK_NOTHROW(AS->build_phase_envelope(
""));
82TEST_CASE(
"Michelsen Flash: Multi-component convergence (4-comp mix)",
"[michelsen][cubic][flash]") {
84 std::vector<double> z = {0.25, 0.25, 0.25, 0.25};
85 AS->set_mole_fractions(z);
91 CHECK(AS->rhomolar() > 0);
107TEST_CASE(
"Michelsen Flash: 7-component natural gas [M82a,M82b]",
"[michelsen][flash][benchmark]") {
110 std::string fluids =
"Methane&Ethane&Propane&n-Butane&n-Pentane&n-Hexane&Nitrogen";
111 std::vector<double> z = {0.9430, 0.0270, 0.0074, 0.0049, 0.0027, 0.0010, 0.0140};
113 SECTION(
"SRK two-phase at 190 K, 4 MPa") {
114 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"SRK", fluids));
115 AS->set_mole_fractions(z);
116 CHECK_NOTHROW(AS->update(
PT_INPUTS, 4e6, 190.0));
122 CHECK(equilibrium_residual(
"SRK", fluids, AS->mole_fractions_liquid_double(), AS->mole_fractions_vapor_double(), 190.0, 4e6) < 1e-6);
124 SECTION(
"PR two-phase at 190 K, 4 MPa") {
125 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"PR", fluids));
126 AS->set_mole_fractions(z);
127 CHECK_NOTHROW(AS->update(
PT_INPUTS, 4e6, 190.0));
131 CHECK(equilibrium_residual(
"PR", fluids, AS->mole_fractions_liquid_double(), AS->mole_fractions_vapor_double(), 190.0, 4e6) < 1e-6);
133 SECTION(
"SRK stable gas at 250 K, 1 MPa") {
134 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"SRK", fluids));
135 AS->set_mole_fractions(z);
136 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e6, 250.0));
137 CHECK(AS->rhomolar() > 0);
139 SECTION(
"PR stable gas at 250 K, 1 MPa") {
140 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"PR", fluids));
141 AS->set_mole_fractions(z);
142 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e6, 250.0));
143 CHECK(AS->rhomolar() > 0);
147TEST_CASE(
"Michelsen Flash: CH4/CO2 binary [MM07 Ch.9]",
"[michelsen][flash][benchmark]") {
150 std::string fluids =
"Methane&CarbonDioxide";
151 std::vector<double> z = {0.5, 0.5};
153 SECTION(
"SRK two-phase at 220 K, 3 MPa") {
154 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"SRK", fluids));
155 AS->set_mole_fractions(z);
156 CHECK_NOTHROW(AS->update(
PT_INPUTS, 3e6, 220.0));
159 SECTION(
"PR two-phase at 220 K, 3 MPa") {
160 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"PR", fluids));
161 AS->set_mole_fractions(z);
162 CHECK_NOTHROW(AS->update(
PT_INPUTS, 3e6, 220.0));
165 SECTION(
"HEOS two-phase at 220 K, 3 MPa") {
166 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS", fluids));
167 AS->set_mole_fractions(z);
168 CHECK_NOTHROW(AS->update(
PT_INPUTS, 3e6, 220.0));
171 SECTION(
"SRK stable supercritical at 300 K, 10 MPa") {
172 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"SRK", fluids));
173 AS->set_mole_fractions(z);
174 CHECK_NOTHROW(AS->update(
PT_INPUTS, 10e6, 300.0));
175 CHECK(AS->rhomolar() > 0);
177 SECTION(
"PR stable supercritical at 300 K, 10 MPa") {
178 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"PR", fluids));
179 AS->set_mole_fractions(z);
180 CHECK_NOTHROW(AS->update(
PT_INPUTS, 10e6, 300.0));
181 CHECK(AS->rhomolar() > 0);
185TEST_CASE(
"Michelsen Flash: CH4/C2H6/CO2 ternary [MM07 Ch.9]",
"[michelsen][flash][benchmark]") {
187 std::string fluids =
"Methane&Ethane&CarbonDioxide";
188 std::vector<double> z = {0.3, 0.3, 0.4};
190 SECTION(
"SRK two-phase at 220 K, 2 MPa") {
191 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"SRK", fluids));
192 AS->set_mole_fractions(z);
193 CHECK_NOTHROW(AS->update(
PT_INPUTS, 2e6, 220.0));
196 SECTION(
"PR two-phase at 220 K, 2 MPa") {
197 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"PR", fluids));
198 AS->set_mole_fractions(z);
199 CHECK_NOTHROW(AS->update(
PT_INPUTS, 2e6, 220.0));
202 SECTION(
"HEOS two-phase at 220 K, 2 MPa") {
203 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS", fluids));
204 AS->set_mole_fractions(z);
205 CHECK_NOTHROW(AS->update(
PT_INPUTS, 2e6, 220.0));
208 SECTION(
"SRK stable gas at 300 K, 1 MPa") {
209 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"SRK", fluids));
210 AS->set_mole_fractions(z);
211 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e6, 300.0));
212 CHECK(AS->rhomolar() > 0);
214 SECTION(
"PR stable gas at 300 K, 1 MPa") {
215 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"PR", fluids));
216 AS->set_mole_fractions(z);
217 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e6, 300.0));
218 CHECK(AS->rhomolar() > 0);
222TEST_CASE(
"Michelsen Flash: CH4/n-C10 wide-boiling binary",
"[michelsen][flash][benchmark]") {
226 std::string fluids =
"Methane&n-Decane";
227 std::vector<double> z = {0.7, 0.3};
229 SECTION(
"SRK two-phase at 350 K, 5 MPa") {
230 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"SRK", fluids));
231 AS->set_mole_fractions(z);
232 CHECK_NOTHROW(AS->update(
PT_INPUTS, 5e6, 350.0));
235 SECTION(
"PR two-phase at 350 K, 5 MPa") {
236 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"PR", fluids));
237 AS->set_mole_fractions(z);
238 CHECK_NOTHROW(AS->update(
PT_INPUTS, 5e6, 350.0));
241 SECTION(
"SRK two-phase at 300 K, 3 MPa (large K-spread)") {
242 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"SRK", fluids));
243 AS->set_mole_fractions(z);
244 CHECK_NOTHROW(AS->update(
PT_INPUTS, 3e6, 300.0));
247 SECTION(
"PR two-phase at 300 K, 3 MPa (large K-spread)") {
248 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"PR", fluids));
249 AS->set_mole_fractions(z);
250 CHECK_NOTHROW(AS->update(
PT_INPUTS, 3e6, 300.0));
255TEST_CASE(
"Michelsen Flash: CH4/H2S binary",
"[michelsen][flash][benchmark]") {
260 std::string fluids =
"Methane&HydrogenSulfide";
261 std::vector<double> z = {0.5, 0.5};
263 SECTION(
"SRK two-phase at 220 K, 2 MPa") {
264 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"SRK", fluids));
265 AS->set_mole_fractions(z);
266 CHECK_NOTHROW(AS->update(
PT_INPUTS, 2e6, 220.0));
269 SECTION(
"PR two-phase at 220 K, 2 MPa") {
270 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"PR", fluids));
271 AS->set_mole_fractions(z);
272 CHECK_NOTHROW(AS->update(
PT_INPUTS, 2e6, 220.0));
277TEST_CASE(
"Michelsen Flash: CH4/C2H6 near mixture critical [M82a]",
"[michelsen][flash][benchmark]") {
281 std::string fluids =
"Methane&Ethane";
282 std::vector<double> z = {0.5, 0.5};
284 SECTION(
"SRK two-phase at 230 K, 4 MPa") {
285 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"SRK", fluids));
286 AS->set_mole_fractions(z);
287 CHECK_NOTHROW(AS->update(
PT_INPUTS, 4e6, 230.0));
290 SECTION(
"PR two-phase at 230 K, 4 MPa") {
291 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"PR", fluids));
292 AS->set_mole_fractions(z);
293 CHECK_NOTHROW(AS->update(
PT_INPUTS, 4e6, 230.0));
298TEST_CASE(
"Michelsen Flash: SRK vs PR cross-validation [M82b]",
"[michelsen][flash][benchmark]") {
302 std::string fluids =
"Methane&Propane";
303 std::vector<double> z = {0.6, 0.4};
305 auto AS_srk = std::shared_ptr<AbstractState>(AbstractState::factory(
"SRK", fluids));
306 auto AS_pr = std::shared_ptr<AbstractState>(AbstractState::factory(
"PR", fluids));
307 AS_srk->set_mole_fractions(z);
308 AS_pr->set_mole_fractions(z);
310 SECTION(
"Two-phase at 250 K, 3 MPa") {
311 CHECK_NOTHROW(AS_srk->update(
PT_INPUTS, 3e6, 250.0));
312 CHECK_NOTHROW(AS_pr->update(
PT_INPUTS, 3e6, 250.0));
316 CHECK(AS_srk->Q() > 0);
317 CHECK(AS_pr->Q() > 0);
321TEST_CASE(
"Michelsen Flash: N2/CH4 cryogenic binary",
"[michelsen][flash][benchmark]") {
325 std::string fluids =
"Nitrogen&Methane";
326 std::vector<double> z = {0.2, 0.8};
328 SECTION(
"SRK stable gas at 200 K, 1 MPa") {
329 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"SRK", fluids));
330 AS->set_mole_fractions(z);
331 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e6, 200.0));
332 CHECK(AS->rhomolar() > 0);
334 SECTION(
"PR stable gas at 200 K, 1 MPa") {
335 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"PR", fluids));
336 AS->set_mole_fractions(z);
337 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e6, 200.0));
338 CHECK(AS->rhomolar() > 0);
342TEST_CASE(
"Michelsen Flash: 11-comp PR hang at high pressure",
"[michelsen][cubic][flash][benchmark]") {
346 std::string fluids =
"Methane&Nitrogen&CO2&Ethane&Propane&Isobutane&Butane&Isopentane&Pentane&Hexane&Heptane";
347 std::vector<double> z = {0.9092, 0.0271, 0.0018, 0.0386, 0.011, 0.0037, 0.0037, 0.00135, 0.00135, 0.0008, 0.0014};
349 SECTION(
"PR at 520 K, 1 MPa (works)") {
350 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"PR", fluids));
351 AS->set_mole_fractions(z);
352 CHECK_NOTHROW(AS->update(
PT_INPUTS, 999999.0, 520.0));
353 CHECK(AS->rhomolar() > 0);
355 SECTION(
"SRK at 520 K, 10.13 MPa (works)") {
356 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"SRK", fluids));
357 AS->set_mole_fractions(z);
358 CHECK_NOTHROW(AS->update(
PT_INPUTS, 10132500.0, 520.0));
359 CHECK(AS->rhomolar() > 0);
361 SECTION(
"PR at 520 K, 10.13 MPa (previously hung)") {
362 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"PR", fluids));
363 AS->set_mole_fractions(z);
364 CHECK_NOTHROW(AS->update(
PT_INPUTS, 10132500.0, 520.0));
365 CHECK(AS->rhomolar() > 0);
369TEST_CASE(
"Michelsen Flash: Issue #3066 (8-comp natural gas density failure)",
"[michelsen][flash][benchmark][3066]") {
374 std::string fluids =
"Methane&Nitrogen&CO2&Ethane&Propane&n-Butane&n-Pentane&IsoButane";
375 std::vector<double> z = {0.9254, 0.007, 0.008, 0.048, 0.0085, 0.0014, 0.0002, 0.0015};
377 SECTION(
"HEOS at 290.15 K, 35.0 bar (previously failed)") {
378 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS", fluids));
379 AS->set_mole_fractions(z);
380 CHECK_NOTHROW(AS->update(
PT_INPUTS, 35.0e5, 290.15));
381 CHECK(AS->rhomolar() > 0);
383 CHECK(AS->rhomolar() > 1000);
384 CHECK(AS->rhomolar() < 3000);
386 SECTION(
"HEOS at 290.15 K, 34.5 bar (previously failed)") {
387 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS", fluids));
388 AS->set_mole_fractions(z);
389 CHECK_NOTHROW(AS->update(
PT_INPUTS, 34.5e5, 290.15));
390 CHECK(AS->rhomolar() > 0);
392 SECTION(
"HEOS at 290.15 K, 34.2 bar (boundary, previously worked)") {
393 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS", fluids));
394 AS->set_mole_fractions(z);
395 CHECK_NOTHROW(AS->update(
PT_INPUTS, 34.2e5, 290.15));
396 CHECK(AS->rhomolar() > 0);
398 SECTION(
"HEOS at 290.15 K, 35.5 bar (boundary, previously worked)") {
399 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS", fluids));
400 AS->set_mole_fractions(z);
401 CHECK_NOTHROW(AS->update(
PT_INPUTS, 35.5e5, 290.15));
402 CHECK(AS->rhomolar() > 0);
406TEST_CASE(
"Legacy Stability: check that legacy algorithm still works",
"[stability][legacy]") {
408 std::vector<double> z = {0.5, 0.5};
409 AS->set_mole_fractions(z);
421TEST_CASE(
"Blind PT flash: Methane/Ethane [0.5/0.5]",
"[michelsen][blind][flash]") {
422 std::vector<double> z = {0.5, 0.5};
423 SECTION(
"SRK gas T=300 P=1e5") {
424 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"SRK",
"Methane&Ethane"));
425 AS->set_mole_fractions(z);
426 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e5, 300.0));
427 CHECK(AS->Q() == -1);
428 CHECK(AS->rhomolar() > 0);
430 SECTION(
"SRK 2ph T=150 P=1e5") {
431 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"SRK",
"Methane&Ethane"));
432 AS->set_mole_fractions(z);
433 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e5, 150.0));
437 SECTION(
"SRK liquid T=100 P=1e5") {
438 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"SRK",
"Methane&Ethane"));
439 AS->set_mole_fractions(z);
440 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e5, 100.0));
441 CHECK(AS->Q() == -1);
442 CHECK(AS->rhomolar() > 0);
444 SECTION(
"PR gas T=300 P=1e5") {
445 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"PR",
"Methane&Ethane"));
446 AS->set_mole_fractions(z);
447 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e5, 300.0));
448 CHECK(AS->Q() == -1);
449 CHECK(AS->rhomolar() > 0);
451 SECTION(
"PR 2ph T=150 P=1e5") {
452 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"PR",
"Methane&Ethane"));
453 AS->set_mole_fractions(z);
454 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e5, 150.0));
458 SECTION(
"PR liquid T=100 P=1e5") {
459 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"PR",
"Methane&Ethane"));
460 AS->set_mole_fractions(z);
461 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e5, 100.0));
462 CHECK(AS->Q() == -1);
463 CHECK(AS->rhomolar() > 0);
465 SECTION(
"HEOS gas T=300 P=1e5") {
466 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS",
"Methane&Ethane"));
467 AS->set_mole_fractions(z);
468 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e5, 300.0));
469 CHECK(AS->Q() == -1);
470 CHECK(AS->rhomolar() > 0);
472 SECTION(
"HEOS 2ph T=150 P=1e5") {
473 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS",
"Methane&Ethane"));
474 AS->set_mole_fractions(z);
475 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e5, 150.0));
479 SECTION(
"HEOS liquid T=100 P=1e5") {
480 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS",
"Methane&Ethane"));
481 AS->set_mole_fractions(z);
482 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e5, 100.0));
483 CHECK(AS->Q() == -1);
484 CHECK(AS->rhomolar() > 0);
488TEST_CASE(
"Blind PT flash: Methane/Propane [0.7/0.3]",
"[michelsen][blind][flash]") {
489 std::vector<double> z = {0.7, 0.3};
490 SECTION(
"SRK gas T=300 P=1e5") {
491 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"SRK",
"Methane&Propane"));
492 AS->set_mole_fractions(z);
493 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e5, 300.0));
494 CHECK(AS->Q() == -1);
495 CHECK(AS->rhomolar() > 0);
497 SECTION(
"SRK 2ph T=150 P=1e5") {
498 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"SRK",
"Methane&Propane"));
499 AS->set_mole_fractions(z);
500 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e5, 150.0));
504 SECTION(
"SRK liquid T=100 P=1e5") {
505 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"SRK",
"Methane&Propane"));
506 AS->set_mole_fractions(z);
507 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e5, 100.0));
508 CHECK(AS->Q() == -1);
509 CHECK(AS->rhomolar() > 0);
511 SECTION(
"PR gas T=300 P=1e5") {
512 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"PR",
"Methane&Propane"));
513 AS->set_mole_fractions(z);
514 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e5, 300.0));
515 CHECK(AS->Q() == -1);
516 CHECK(AS->rhomolar() > 0);
518 SECTION(
"PR 2ph T=150 P=1e5") {
519 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"PR",
"Methane&Propane"));
520 AS->set_mole_fractions(z);
521 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e5, 150.0));
525 SECTION(
"PR liquid T=100 P=1e5") {
526 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"PR",
"Methane&Propane"));
527 AS->set_mole_fractions(z);
528 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e5, 100.0));
529 CHECK(AS->Q() == -1);
530 CHECK(AS->rhomolar() > 0);
532 SECTION(
"HEOS gas T=300 P=1e5") {
533 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS",
"Methane&Propane"));
534 AS->set_mole_fractions(z);
535 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e5, 300.0));
536 CHECK(AS->Q() == -1);
537 CHECK(AS->rhomolar() > 0);
539 SECTION(
"HEOS 2ph T=150 P=1e5") {
540 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS",
"Methane&Propane"));
541 AS->set_mole_fractions(z);
542 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e5, 150.0));
546 SECTION(
"HEOS liquid T=100 P=1e5") {
547 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS",
"Methane&Propane"));
548 AS->set_mole_fractions(z);
549 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e5, 100.0));
550 CHECK(AS->Q() == -1);
551 CHECK(AS->rhomolar() > 0);
555TEST_CASE(
"Blind PT flash: Nitrogen/Oxygen [0.79/0.21]",
"[michelsen][blind][flash]") {
556 std::vector<double> z = {0.79, 0.21};
557 SECTION(
"SRK gas T=300 P=1e5") {
558 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"SRK",
"Nitrogen&Oxygen"));
559 AS->set_mole_fractions(z);
560 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e5, 300.0));
561 CHECK(AS->Q() == -1);
562 CHECK(AS->rhomolar() > 0);
564 SECTION(
"SRK 2ph T=80 P=1e5") {
565 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"SRK",
"Nitrogen&Oxygen"));
566 AS->set_mole_fractions(z);
567 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e5, 80.0));
571 SECTION(
"SRK liquid T=75 P=1e5") {
572 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"SRK",
"Nitrogen&Oxygen"));
573 AS->set_mole_fractions(z);
574 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e5, 75.0));
575 CHECK(AS->Q() == -1);
576 CHECK(AS->rhomolar() > 0);
578 SECTION(
"PR gas T=300 P=1e5") {
579 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"PR",
"Nitrogen&Oxygen"));
580 AS->set_mole_fractions(z);
581 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e5, 300.0));
582 CHECK(AS->Q() == -1);
583 CHECK(AS->rhomolar() > 0);
585 SECTION(
"PR 2ph T=80 P=1e5") {
586 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"PR",
"Nitrogen&Oxygen"));
587 AS->set_mole_fractions(z);
588 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e5, 80.0));
592 SECTION(
"PR liquid T=75 P=1e5") {
593 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"PR",
"Nitrogen&Oxygen"));
594 AS->set_mole_fractions(z);
595 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e5, 75.0));
596 CHECK(AS->Q() == -1);
597 CHECK(AS->rhomolar() > 0);
599 SECTION(
"HEOS gas T=300 P=1e5") {
600 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS",
"Nitrogen&Oxygen"));
601 AS->set_mole_fractions(z);
602 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e5, 300.0));
603 CHECK(AS->Q() == -1);
604 CHECK(AS->rhomolar() > 0);
606 SECTION(
"HEOS 2ph T=80 P=1e5") {
607 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS",
"Nitrogen&Oxygen"));
608 AS->set_mole_fractions(z);
609 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e5, 80.0));
613 SECTION(
"HEOS liquid T=75 P=1e5") {
614 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS",
"Nitrogen&Oxygen"));
615 AS->set_mole_fractions(z);
616 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e5, 75.0));
617 CHECK(AS->Q() == -1);
618 CHECK(AS->rhomolar() > 20000);
622TEST_CASE(
"Blind flash: N2/O2 subcooled liquid phase selection",
"[michelsen][blind][flash]") {
627 std::vector<double> z = {0.79, 0.21};
630 AS->set_mole_fractions(z);
632 CHECK(AS->Q() == -1);
634 CHECK(AS->rhomolar() > 20000);
636 SECTION(
"Imposed liquid agrees with blind") {
638 AS_imp->set_mole_fractions(z);
641 AS_imp->unspecify_phase();
642 CHECK(AS_imp->rhomolar() > 20000);
643 CHECK(AS_imp->rhomolar() == Catch::Approx(AS->rhomolar()).epsilon(1e-6));
646 SECTION(
"Imposed gas gives low density") {
648 AS_imp->set_mole_fractions(z);
651 AS_imp->unspecify_phase();
652 CHECK(AS_imp->rhomolar() < 500);
656TEST_CASE(
"Blind PT flash: N2/CH4/C2H6/C3H8 [0.1/0.5/0.25/0.15]",
"[michelsen][blind][flash]") {
657 std::vector<double> z = {0.1, 0.5, 0.25, 0.15};
658 SECTION(
"SRK gas T=300 P=1e5") {
659 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"SRK",
"Nitrogen&Methane&Ethane&Propane"));
660 AS->set_mole_fractions(z);
661 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e5, 300.0));
662 CHECK(AS->Q() == -1);
663 CHECK(AS->rhomolar() > 0);
665 SECTION(
"SRK 2ph T=145 P=1e5") {
666 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"SRK",
"Nitrogen&Methane&Ethane&Propane"));
667 AS->set_mole_fractions(z);
668 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e5, 145.0));
672 SECTION(
"SRK liquid T=80 P=1e5") {
673 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"SRK",
"Nitrogen&Methane&Ethane&Propane"));
674 AS->set_mole_fractions(z);
675 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e5, 80.0));
676 CHECK(AS->Q() == -1);
677 CHECK(AS->rhomolar() > 0);
679 SECTION(
"PR gas T=300 P=1e5") {
680 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"PR",
"Nitrogen&Methane&Ethane&Propane"));
681 AS->set_mole_fractions(z);
682 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e5, 300.0));
683 CHECK(AS->Q() == -1);
684 CHECK(AS->rhomolar() > 0);
686 SECTION(
"PR 2ph T=145 P=1e5") {
687 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"PR",
"Nitrogen&Methane&Ethane&Propane"));
688 AS->set_mole_fractions(z);
689 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e5, 145.0));
693 SECTION(
"PR liquid T=80 P=1e5") {
694 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"PR",
"Nitrogen&Methane&Ethane&Propane"));
695 AS->set_mole_fractions(z);
696 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e5, 80.0));
697 CHECK(AS->Q() == -1);
698 CHECK(AS->rhomolar() > 0);
700 SECTION(
"HEOS gas T=300 P=1e5") {
701 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS",
"Nitrogen&Methane&Ethane&Propane"));
702 AS->set_mole_fractions(z);
703 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e5, 300.0));
704 CHECK(AS->Q() == -1);
705 CHECK(AS->rhomolar() > 0);
707 SECTION(
"HEOS 2ph T=145 P=1e5") {
708 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS",
"Nitrogen&Methane&Ethane&Propane"));
709 AS->set_mole_fractions(z);
710 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e5, 145.0));
714 SECTION(
"HEOS liquid T=80 P=1e5") {
715 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS",
"Nitrogen&Methane&Ethane&Propane"));
716 AS->set_mole_fractions(z);
717 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e5, 80.0));
718 CHECK(AS->Q() == -1);
719 CHECK(AS->rhomolar() > 0);
723TEST_CASE(
"Legacy Flash: check that legacy Jacobian solver still works",
"[flash][legacy]") {
725 std::vector<double> z = {0.5, 0.5};
726 AS->set_mole_fractions(z);
742TEST_CASE(
"PT flash with built phase envelope",
"[michelsen][phase_envelope]") {
747 std::vector<double> z = {0.85, 0.15};
751 AS_env->set_mole_fractions(z);
752 AS_env->build_phase_envelope(
"");
756 AS_blind->set_mole_fractions(z);
758 SECTION(
"Gas side: 1 MPa, 250 K") {
762 CHECK(AS_env->Q() == -1);
764 CHECK(AS_env->rhomolar() == Catch::Approx(AS_blind->rhomolar()).epsilon(1e-6));
767 SECTION(
"Liquid side: 3 MPa, 120 K") {
771 CHECK(AS_env->Q() == -1);
777 CHECK(AS_env->rhomolar() > 20000);
778 CHECK(AS_blind->rhomolar() > 20000);
781 SECTION(
"Two-phase: 2 MPa, 180 K") {
788 CHECK(AS_env->Q() > 0.0);
789 CHECK(AS_env->Q() < 1.0);
790 CHECK(AS_env->rhomolar() == Catch::Approx(AS_blind->rhomolar()).epsilon(1e-4));
794TEST_CASE(
"PQ/QT flash with built envelope at 0<Q<1 (GH #3192)",
"[michelsen][phase_envelope]") {
803 std::vector<double> z = {0.5, 0.5};
805 auto make_pe = [&]() {
806 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS",
"Methane&Ethane"));
807 AS->set_mole_fractions(z);
808 AS->build_phase_envelope(
"");
811 auto make_blind = [&]() {
812 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS",
"Methane&Ethane"));
813 AS->set_mole_fractions(z);
817 SECTION(
"PQ flash, P=1e5, Q=0.5 (exact reporter repro)") {
819 auto ref = make_blind();
822 REQUIRE(AS->get_phase_envelope_data().built);
823 CHECK_NOTHROW(AS->update(
PQ_INPUTS, 1e5, 0.5));
826 CHECK(std::isfinite(AS->T()));
827 CHECK(AS->T() == Catch::Approx(ref->T()).epsilon(1e-6));
828 CHECK(AS->rhomolar() == Catch::Approx(ref->rhomolar()).epsilon(1e-6));
831 SECTION(
"QT flash, Q=0.5, T=180 K") {
833 auto ref = make_blind();
834 REQUIRE(AS->get_phase_envelope_data().built);
835 CHECK_NOTHROW(AS->update(
QT_INPUTS, 0.5, 180.0));
838 CHECK(AS->p() == Catch::Approx(ref->p()).epsilon(1e-6));
839 CHECK(AS->rhomolar() == Catch::Approx(ref->rhomolar()).epsilon(1e-6));
842 SECTION(
"Dew/bubble (Q==0, Q==1) still use the envelope fast path and stay accurate") {
844 auto ref = make_blind();
845 REQUIRE(AS->get_phase_envelope_data().built);
846 CHECK_NOTHROW(AS->update(
PQ_INPUTS, 1e5, 1.0));
848 CHECK(AS->T() == Catch::Approx(ref->T()).epsilon(1e-6));
849 CHECK_NOTHROW(AS->update(
PQ_INPUTS, 1e5, 0.0));
851 CHECK(AS->T() == Catch::Approx(ref->T()).epsilon(1e-6));
854 SECTION(
"pq -> pt -> q roundtrip recovers the imposed quality") {
859 REQUIRE(AS->get_phase_envelope_data().built);
861 const double T_2ph = AS->T();
862 auto rt = make_blind();
864 CHECK(rt->Q() == Catch::Approx(0.5).epsilon(1e-4));
868TEST_CASE(
"PQ flash with built envelope, ternary two-phase (GH #3192, N>=3)",
"[michelsen][phase_envelope]") {
872 std::vector<double> z = {0.4, 0.35, 0.25};
873 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS",
"Methane&Ethane&Propane"));
874 AS->set_mole_fractions(z);
875 AS->build_phase_envelope(
"");
878 REQUIRE(AS->get_phase_envelope_data().built);
879 auto ref = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS",
"Methane&Ethane&Propane"));
880 ref->set_mole_fractions(z);
882 CHECK_NOTHROW(AS->update(
PQ_INPUTS, 1e6, 0.5));
885 CHECK(std::isfinite(AS->T()));
886 CHECK(AS->T() == Catch::Approx(ref->T()).epsilon(1e-5));
887 CHECK(AS->rhomolar() == Catch::Approx(ref->rhomolar()).epsilon(1e-5));
903TEST_CASE(
"PE flash: Methane/Ethane [0.5/0.5]",
"[michelsen][phase_envelope]") {
905 std::vector<double> z = {0.5, 0.5};
907 auto make_pe = [&]() {
908 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS",
"Methane&Ethane"));
909 AS->set_mole_fractions(z);
910 AS->build_phase_envelope(
"");
913 auto make_blind = [&]() {
914 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS",
"Methane&Ethane"));
915 AS->set_mole_fractions(z);
919 SECTION(
"HEOS gas T=300 P=1e5") {
921 auto ref = make_blind();
922 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e5, 300.0));
924 CHECK(AS->Q() == -1);
925 CHECK(AS->rhomolar() > 0);
926 CHECK(AS->rhomolar() == Catch::Approx(ref->rhomolar()).epsilon(1e-6));
928 SECTION(
"HEOS 2ph T=150 P=1e5") {
930 auto ref = make_blind();
931 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e5, 150.0));
936 CHECK(AS->rhomolar() == Catch::Approx(ref->rhomolar()).epsilon(1e-4));
938 SECTION(
"HEOS liquid T=100 P=1e5") {
940 auto ref = make_blind();
941 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e5, 100.0));
943 CHECK(AS->Q() == -1);
944 CHECK(AS->rhomolar() > 0);
947 CHECK(AS->rhomolar() == Catch::Approx(ref->rhomolar()).epsilon(1e-3));
951TEST_CASE(
"PE flash: Methane/Propane [0.7/0.3]",
"[michelsen][phase_envelope]") {
954 std::vector<double> z = {0.7, 0.3};
956 auto make_pe = [&]() {
957 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS",
"Methane&Propane"));
958 AS->set_mole_fractions(z);
959 AS->build_phase_envelope(
"");
962 auto make_blind = [&]() {
963 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS",
"Methane&Propane"));
964 AS->set_mole_fractions(z);
968 SECTION(
"HEOS gas T=300 P=1e5") {
970 auto ref = make_blind();
971 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e5, 300.0));
973 CHECK(AS->Q() == -1);
974 CHECK(AS->rhomolar() == Catch::Approx(ref->rhomolar()).epsilon(1e-6));
976 SECTION(
"HEOS 2ph T=150 P=1e5") {
978 auto ref = make_blind();
979 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e5, 150.0));
984 CHECK(AS->rhomolar() == Catch::Approx(ref->rhomolar()).epsilon(1e-4));
986 SECTION(
"HEOS liquid T=100 P=1e5") {
988 auto ref = make_blind();
989 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e5, 100.0));
991 CHECK(AS->Q() == -1);
992 CHECK(AS->rhomolar() == Catch::Approx(ref->rhomolar()).epsilon(1e-6));
996TEST_CASE(
"PE flash: Nitrogen/Oxygen [0.79/0.21]",
"[michelsen][phase_envelope]") {
999 std::vector<double> z = {0.79, 0.21};
1001 auto make_pe = [&]() {
1002 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS",
"Nitrogen&Oxygen"));
1003 AS->set_mole_fractions(z);
1004 AS->build_phase_envelope(
"");
1007 auto make_blind = [&]() {
1008 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS",
"Nitrogen&Oxygen"));
1009 AS->set_mole_fractions(z);
1013 SECTION(
"HEOS gas T=300 P=1e5") {
1014 auto AS = make_pe();
1015 auto ref = make_blind();
1016 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e5, 300.0));
1018 CHECK(AS->Q() == -1);
1019 CHECK(AS->rhomolar() == Catch::Approx(ref->rhomolar()).epsilon(1e-6));
1021 SECTION(
"HEOS liquid T=75 P=1e5") {
1025 auto AS = make_pe();
1026 auto ref = make_blind();
1027 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e5, 75.0));
1029 CHECK(AS->Q() == -1);
1030 CHECK(AS->rhomolar() > 20000);
1031 CHECK(ref->rhomolar() > 20000);
1032 CHECK(AS->rhomolar() == Catch::Approx(ref->rhomolar()).epsilon(1e-3));
1036TEST_CASE(
"PE flash: N2/CH4/C2H6/C3H8 [0.1/0.5/0.25/0.15]",
"[michelsen][phase_envelope]") {
1039 std::vector<double> z = {0.1, 0.5, 0.25, 0.15};
1041 auto make_pe = [&]() {
1042 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS",
"Nitrogen&Methane&Ethane&Propane"));
1043 AS->set_mole_fractions(z);
1044 AS->build_phase_envelope(
"");
1047 auto make_blind = [&]() {
1048 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS",
"Nitrogen&Methane&Ethane&Propane"));
1049 AS->set_mole_fractions(z);
1053 SECTION(
"HEOS gas T=300 P=1e5") {
1054 auto AS = make_pe();
1055 auto ref = make_blind();
1056 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e5, 300.0));
1058 CHECK(AS->Q() == -1);
1059 CHECK(AS->rhomolar() == Catch::Approx(ref->rhomolar()).epsilon(1e-6));
1061 SECTION(
"HEOS 2ph T=145 P=1e5") {
1062 auto AS = make_pe();
1063 auto ref = make_blind();
1064 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e5, 145.0));
1069 CHECK(AS->rhomolar() == Catch::Approx(ref->rhomolar()).epsilon(1e-4));
1071 SECTION(
"HEOS liquid T=80 P=1e5") {
1072 auto AS = make_pe();
1073 auto ref = make_blind();
1074 CHECK_NOTHROW(AS->update(
PT_INPUTS, 1e5, 80.0));
1076 CHECK(AS->Q() == -1);
1077 CHECK(AS->rhomolar() == Catch::Approx(ref->rhomolar()).epsilon(1e-6));
1081TEST_CASE(
"Methanol-benzene PT flash at problematic compositions",
"[michelsen][meoh_benz]") {
1092 for (
double x : {0.54, 0.56, 0.58, 0.76, 0.78, 0.80}) {
1093 DYNAMIC_SECTION(
"x_methanol = " << x) {
1094 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS",
"methanol&benzene"));
1095 AS->set_mole_fractions({x, 1.0 - x});
1096 REQUIRE_NOTHROW(AS->update(
PT_INPUTS, 101325, 308.15));
1097 double rho = AS->rhomolar();
1099 CHECK(std::isfinite(rho));
1101 CHECK(std::isfinite(AS->gibbsmolar()));
1103 CHECK(AS->Q() == -1);
1114TEST_CASE(
"Blind PT flash: near-dew two-phase classification (CoolProp-zgpy)",
"[michelsen][blind][flash][zgpy]") {
1123 const std::string fluids =
"Nitrogen&Methane&Ethane&Butane&Pentane";
1124 const std::vector<double> z = {0.3797, 0.3225, 0.278, 0.0014, 0.0184};
1125 const double p = 3e5,
T = 220.0;
1127 for (
const std::string& backend : {std::string(
"SRK"), std::string(
"PR")}) {
1128 DYNAMIC_SECTION(backend <<
" two-phase at 220 K, 3 bar") {
1129 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(backend, fluids));
1130 AS->set_mole_fractions(z);
1131 REQUIRE_NOTHROW(AS->update(
PT_INPUTS, p,
T));
1135 CHECK(AS->Q() > 0.0);
1136 CHECK(AS->Q() < 1.0);
1138 std::vector<double> x = AS->mole_fractions_liquid();
1139 std::vector<double> y = AS->mole_fractions_vapor();
1144 for (std::size_t i = 0; i < x.size(); ++i)
1145 spread = std::max(spread, std::abs(x[i] - y[i]));
1146 CHECK(spread > 1e-2);
1149 CHECK(equilibrium_residual(backend, fluids, x, y,
T, p) < 1e-6);
1161TEST_CASE(
"Stability sweep: blind two-phase classification + fugacity (CoolProp-zgpy)",
"[michelsen][stability_sweep][zgpy]") {
1165 std::vector<double> z;
1168 std::vector<SweepCase> cases = {
1169 {
"Nitrogen&Methane&Ethane&Butane&Pentane", {0.3797, 0.3225, 0.278, 0.0014, 0.0184}, 3e5},
1170 {
"Nitrogen&Methane&Ethane&Propane", {0.1, 0.5, 0.25, 0.15}, 1e6},
1171 {
"Methane&Ethane", {0.5, 0.5}, 1e6},
1174 for (
const std::string backend : {std::string(
"SRK"), std::string(
"PR"), std::string(
"HEOS")}) {
1175 for (
const SweepCase& c : cases) {
1176 double Tbub = -1, Tdew = -1;
1178 auto S = std::shared_ptr<AbstractState>(AbstractState::factory(backend, c.fluids));
1179 S->set_mole_fractions(c.z);
1187 if (!(Tbub > 0 && Tdew > Tbub))
continue;
1189 int misclass = 0, bad_fug = 0, trivial = 0;
1190 for (
int k = 0; k < NT; ++k) {
1191 double T = Tbub + (Tdew - Tbub) * (k + 0.5) / NT;
1192 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(backend, c.fluids));
1193 AS->set_mole_fractions(c.z);
1205 std::vector<double> x = AS->mole_fractions_liquid(), y = AS->mole_fractions_vapor();
1207 for (std::size_t i = 0; i < x.size(); ++i)
1208 spread = std::max(spread, std::abs(x[i] - y[i]));
1211 else if (equilibrium_residual(backend, c.fluids, x, y,
T, c.p) > 1e-6)
1217 WARN(backend <<
" " << c.fluids <<
" P=" << c.p <<
" misclass=" << misclass <<
" bad_fug=" << bad_fug <<
" trivial=" << trivial <<
" / "
1219 CHECK(trivial == 0);
1226 for (
double Tsp : {Tdew + 30.0, Tdew + 80.0}) {
1227 if (Tsp <= 0)
continue;
1228 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(backend, c.fluids));
1229 AS->set_mole_fractions(c.z);
1235 INFO(backend <<
" " << c.fluids <<
" single-phase check at T=" << Tsp);
1247static void hsu_p_roundtrip(
const std::string& backend,
const std::string& fluids,
const std::vector<double>& z,
double P,
double T,
1250 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(backend, fluids));
1251 AS->set_mole_fractions(z);
1253 double T_ref = AS->T();
1254 double rho_ref = AS->rhomolar();
1257 switch (flash_pair) {
1259 y_ref = AS->hmolar();
1262 y_ref = AS->smolar();
1265 y_ref = AS->umolar();
1268 throw ValueError(
"unsupported flash pair in hsu_p_roundtrip");
1271 auto AS2 = std::shared_ptr<AbstractState>(AbstractState::factory(backend, fluids));
1272 AS2->set_mole_fractions(z);
1275 AS2->update(flash_pair, y_ref, P);
1277 AS2->update(flash_pair, P, y_ref);
1279 CHECK(AS2->T() == Catch::Approx(T_ref).epsilon(eps));
1280 CHECK(AS2->rhomolar() == Catch::Approx(rho_ref).epsilon(eps));
1283TEST_CASE(
"HSU_P flash: mixture HP round-trip",
"[michelsen][flash][HSU_P]") {
1284 SECTION(
"N2/O2 gas T=300 P=1e5") {
1285 hsu_p_roundtrip(
"HEOS",
"Nitrogen&Oxygen", {0.79, 0.21}, 1e5, 300.0,
HmolarP_INPUTS);
1287 SECTION(
"N2/O2 liquid T=75 P=1e5") {
1288 hsu_p_roundtrip(
"HEOS",
"Nitrogen&Oxygen", {0.79, 0.21}, 1e5, 75.0,
HmolarP_INPUTS);
1290 SECTION(
"CH4/C2H6 gas T=250 P=1e6") {
1291 hsu_p_roundtrip(
"HEOS",
"Methane&Ethane", {0.85, 0.15}, 1e6, 250.0,
HmolarP_INPUTS);
1293 SECTION(
"4-component gas T=300 P=1e5") {
1294 hsu_p_roundtrip(
"HEOS",
"Nitrogen&Methane&Ethane&Propane", {0.1, 0.5, 0.25, 0.15}, 1e5, 300.0,
HmolarP_INPUTS);
1298TEST_CASE(
"HSU_P flash: mixture SP round-trip",
"[michelsen][flash][HSU_P]") {
1299 SECTION(
"N2/O2 gas T=300 P=1e5") {
1300 hsu_p_roundtrip(
"HEOS",
"Nitrogen&Oxygen", {0.79, 0.21}, 1e5, 300.0,
PSmolar_INPUTS);
1302 SECTION(
"N2/O2 liquid T=75 P=1e5") {
1303 hsu_p_roundtrip(
"HEOS",
"Nitrogen&Oxygen", {0.79, 0.21}, 1e5, 75.0,
PSmolar_INPUTS);
1305 SECTION(
"CH4/C2H6 gas T=250 P=1e6") {
1306 hsu_p_roundtrip(
"HEOS",
"Methane&Ethane", {0.85, 0.15}, 1e6, 250.0,
PSmolar_INPUTS);
1308 SECTION(
"4-component gas T=300 P=1e5") {
1309 hsu_p_roundtrip(
"HEOS",
"Nitrogen&Methane&Ethane&Propane", {0.1, 0.5, 0.25, 0.15}, 1e5, 300.0,
PSmolar_INPUTS);
1313TEST_CASE(
"HSU_P flash: mixture UP round-trip",
"[michelsen][flash][HSU_P]") {
1314 SECTION(
"N2/O2 gas T=300 P=1e5") {
1315 hsu_p_roundtrip(
"HEOS",
"Nitrogen&Oxygen", {0.79, 0.21}, 1e5, 300.0,
PUmolar_INPUTS);
1317 SECTION(
"N2/O2 liquid T=75 P=1e5") {
1318 hsu_p_roundtrip(
"HEOS",
"Nitrogen&Oxygen", {0.79, 0.21}, 1e5, 75.0,
PUmolar_INPUTS);
1320 SECTION(
"CH4/C2H6 gas T=250 P=1e6") {
1321 hsu_p_roundtrip(
"HEOS",
"Methane&Ethane", {0.85, 0.15}, 1e6, 250.0,
PUmolar_INPUTS);
1323 SECTION(
"4-component gas T=300 P=1e5") {
1324 hsu_p_roundtrip(
"HEOS",
"Nitrogen&Methane&Ethane&Propane", {0.1, 0.5, 0.25, 0.15}, 1e5, 300.0,
PUmolar_INPUTS);
1328TEST_CASE(
"HSU_P flash: mixture two-phase HP round-trip",
"[michelsen][flash][HSU_P]") {
1333 SECTION(
"N2/O2 two-phase T=80 P=1e5") {
1334 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS",
"Nitrogen&Oxygen"));
1335 AS->set_mole_fractions({0.79, 0.21});
1338 double T_ref = AS->T();
1339 double rho_ref = AS->rhomolar();
1340 double Q_ref = AS->Q();
1341 double h_ref = AS->hmolar();
1343 auto AS2 = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS",
"Nitrogen&Oxygen"));
1344 AS2->set_mole_fractions({0.79, 0.21});
1346 CHECK(AS2->T() == Catch::Approx(T_ref).epsilon(1e-4));
1347 CHECK(AS2->rhomolar() == Catch::Approx(rho_ref).epsilon(1e-4));
1348 CHECK(AS2->Q() == Catch::Approx(Q_ref).margin(0.01));
1364TEST_CASE(
"HSU_P flash: CO2/Water/N2/Ar/O2 mixture no silent wrong answer (GitHub #3148)",
"[michelsen][flash][HSU_P]") {
1366 const std::string fluids =
"CarbonDioxide&Water&Nitrogen&Argon&Oxygen";
1367 const std::vector<double> z = {0.90, 0.02, 0.04, 0.01, 0.03};
1368 const double P = 2.05e6;
1373 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS", fluids));
1374 AS->set_mole_fractions(z);
1379 AS->update(pair, value, P);
1381 AS->update(pair, P, value);
1386 const double got = AS->keyed_output(key);
1387 CAPTURE(value, got);
1388 CHECK(got == Catch::Approx(value).epsilon(1e-6));
1393 for (
double T_true : {255.0, 260.0, 270.0, 280.0, 290.0, 300.0}) {
1394 DYNAMIC_SECTION(
"HP at T_true = " << T_true <<
" K") {
1395 auto ref = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS", fluids));
1396 ref->set_mole_fractions(z);
1402 SECTION(
"SP at T_true = 270 K") {
1403 auto ref = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS", fluids));
1404 ref->set_mole_fractions(z);
1415static void dhsu_t_roundtrip(
const std::string& backend,
const std::string& fluids,
const std::vector<double>& z,
double P,
double T,
1418 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(backend, fluids));
1419 AS->set_mole_fractions(z);
1421 double P_ref = AS->p();
1422 double rho_ref = AS->rhomolar();
1425 switch (flash_pair) {
1427 y_ref = AS->rhomolar();
1430 y_ref = AS->hmolar();
1433 y_ref = AS->smolar();
1436 y_ref = AS->umolar();
1439 throw ValueError(
"unsupported flash pair in dhsu_t_roundtrip");
1442 auto AS2 = std::shared_ptr<AbstractState>(AbstractState::factory(backend, fluids));
1443 AS2->set_mole_fractions(z);
1446 AS2->update(flash_pair,
T, y_ref);
1448 AS2->update(flash_pair, y_ref,
T);
1450 CHECK(AS2->p() == Catch::Approx(P_ref).epsilon(eps));
1451 CHECK(AS2->rhomolar() == Catch::Approx(rho_ref).epsilon(eps));
1454TEST_CASE(
"DHSU_T flash: mixture DT round-trip",
"[michelsen][flash][DHSU_T]") {
1455 SECTION(
"N2/O2 gas T=300 P=1e5") {
1456 dhsu_t_roundtrip(
"HEOS",
"Nitrogen&Oxygen", {0.79, 0.21}, 1e5, 300.0,
DmolarT_INPUTS);
1458 SECTION(
"N2/O2 liquid T=75 P=1e5") {
1459 dhsu_t_roundtrip(
"HEOS",
"Nitrogen&Oxygen", {0.79, 0.21}, 1e5, 75.0,
DmolarT_INPUTS);
1461 SECTION(
"CH4/C2H6 gas T=250 P=1e6") {
1462 dhsu_t_roundtrip(
"HEOS",
"Methane&Ethane", {0.85, 0.15}, 1e6, 250.0,
DmolarT_INPUTS);
1466TEST_CASE(
"DHSU_T flash: mixture HT round-trip",
"[michelsen][flash][DHSU_T]") {
1467 SECTION(
"N2/O2 gas T=300 P=1e5") {
1468 dhsu_t_roundtrip(
"HEOS",
"Nitrogen&Oxygen", {0.79, 0.21}, 1e5, 300.0,
HmolarT_INPUTS);
1470 SECTION(
"N2/O2 liquid T=75 P=1e5") {
1471 dhsu_t_roundtrip(
"HEOS",
"Nitrogen&Oxygen", {0.79, 0.21}, 1e5, 75.0,
HmolarT_INPUTS);
1473 SECTION(
"CH4/C2H6 gas T=250 P=1e6") {
1474 dhsu_t_roundtrip(
"HEOS",
"Methane&Ethane", {0.85, 0.15}, 1e6, 250.0,
HmolarT_INPUTS);
1476 SECTION(
"4-component gas T=300 P=1e5") {
1477 dhsu_t_roundtrip(
"HEOS",
"Nitrogen&Methane&Ethane&Propane", {0.1, 0.5, 0.25, 0.15}, 1e5, 300.0,
HmolarT_INPUTS);
1481TEST_CASE(
"DHSU_T flash: mixture ST round-trip",
"[michelsen][flash][DHSU_T]") {
1482 SECTION(
"N2/O2 gas T=300 P=1e5") {
1483 dhsu_t_roundtrip(
"HEOS",
"Nitrogen&Oxygen", {0.79, 0.21}, 1e5, 300.0,
SmolarT_INPUTS);
1485 SECTION(
"N2/O2 liquid T=75 P=1e5") {
1486 dhsu_t_roundtrip(
"HEOS",
"Nitrogen&Oxygen", {0.79, 0.21}, 1e5, 75.0,
SmolarT_INPUTS);
1488 SECTION(
"CH4/C2H6 gas T=250 P=1e6") {
1489 dhsu_t_roundtrip(
"HEOS",
"Methane&Ethane", {0.85, 0.15}, 1e6, 250.0,
SmolarT_INPUTS);
1491 SECTION(
"4-component gas T=300 P=1e5") {
1492 dhsu_t_roundtrip(
"HEOS",
"Nitrogen&Methane&Ethane&Propane", {0.1, 0.5, 0.25, 0.15}, 1e5, 300.0,
SmolarT_INPUTS);
1496TEST_CASE(
"DHSU_T flash: mixture UT round-trip",
"[michelsen][flash][DHSU_T]") {
1497 SECTION(
"N2/O2 gas T=300 P=1e5") {
1498 dhsu_t_roundtrip(
"HEOS",
"Nitrogen&Oxygen", {0.79, 0.21}, 1e5, 300.0,
TUmolar_INPUTS);
1500 SECTION(
"N2/O2 liquid T=75 P=1e5") {
1501 dhsu_t_roundtrip(
"HEOS",
"Nitrogen&Oxygen", {0.79, 0.21}, 1e5, 75.0,
TUmolar_INPUTS);
1503 SECTION(
"CH4/C2H6 gas T=250 P=1e6") {
1504 dhsu_t_roundtrip(
"HEOS",
"Methane&Ethane", {0.85, 0.15}, 1e6, 250.0,
TUmolar_INPUTS);
1506 SECTION(
"4-component gas T=300 P=1e5") {
1507 dhsu_t_roundtrip(
"HEOS",
"Nitrogen&Methane&Ethane&Propane", {0.1, 0.5, 0.25, 0.15}, 1e5, 300.0,
TUmolar_INPUTS);
1519TEST_CASE(
"HSU_P flash: near saturation Propane/Butane",
"[michelsen][flash][HSU_P][saturation]") {
1522 const std::string fluids =
"Propane&Butane";
1523 const std::vector<double> z = {0.5, 0.5};
1524 const double P = 1e5;
1525 const double TOL = 0.1;
1527 auto sat = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS", fluids));
1528 sat->set_mole_fractions(z);
1530 const double T_bub = sat->T();
1532 const double T_dew = sat->T();
1540 std::vector<Case> cases = {
1546 for (
auto& c : cases) {
1547 auto ref = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS", fluids));
1548 ref->set_mole_fractions(z);
1553 ref->specify_phase(c.ph);
1556 const double H_ref = ref->hmass();
1557 const double S_ref = ref->smass();
1558 const double U_ref = ref->umass();
1559 ref->unspecify_phase();
1561 DYNAMIC_SECTION(c.label +
" HP") {
1562 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS", fluids));
1563 AS->set_mole_fractions(z);
1565 CHECK(std::abs(AS->T() - c.T) < TOL);
1567 DYNAMIC_SECTION(c.label +
" SP") {
1568 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS", fluids));
1569 AS->set_mole_fractions(z);
1571 CHECK(std::abs(AS->T() - c.T) < TOL);
1573 DYNAMIC_SECTION(c.label +
" UP") {
1574 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS", fluids));
1575 AS->set_mole_fractions(z);
1577 CHECK(std::abs(AS->T() - c.T) < TOL);
1582TEST_CASE(
"HSU_P flash: near saturation 5-component N2-HC",
"[michelsen][flash][HSU_P][saturation]") {
1588 const std::string fluids =
"Nitrogen&Methane&Ethane&Butane&Pentane";
1589 const std::vector<double> z = {0.3797, 0.3225, 0.278, 0.0014, 0.0184};
1590 const double P = 3e5;
1591 const double TOL = 0.1;
1593 auto sat = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS", fluids));
1594 sat->set_mole_fractions(z);
1596 const double T_bub = sat->T();
1598 const double T_dew = sat->T();
1606 std::vector<Case> cases = {
1610 {
"superheated T_dew+1", T_dew + 1.0,
iphase_gas},
1613 for (
auto& c : cases) {
1614 auto ref = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS", fluids));
1615 ref->set_mole_fractions(z);
1617 ref->specify_phase(c.ph);
1620 const double H_ref = ref->hmass();
1621 const double S_ref = ref->smass();
1622 const double U_ref = ref->umass();
1623 ref->unspecify_phase();
1625 DYNAMIC_SECTION(c.label +
" HP") {
1626 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS", fluids));
1627 AS->set_mole_fractions(z);
1629 CHECK(std::abs(AS->T() - c.T) < TOL);
1631 DYNAMIC_SECTION(c.label +
" SP") {
1632 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS", fluids));
1633 AS->set_mole_fractions(z);
1635 CHECK(std::abs(AS->T() - c.T) < TOL);
1637 DYNAMIC_SECTION(c.label +
" UP") {
1638 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS", fluids));
1639 AS->set_mole_fractions(z);
1641 CHECK(std::abs(AS->T() - c.T) < TOL);
1646TEST_CASE(
"PT flash: 5-component two-phase consistency",
"[michelsen][flash][VLE]") {
1654 const std::string fluids =
"Nitrogen&Methane&Ethane&Butane&Pentane";
1655 const std::vector<double> z = {0.3797, 0.3225, 0.278, 0.0014, 0.0184};
1656 const double P = 3e5;
1658 const std::size_t NC = z.size();
1659 const double FUG_TOL = 1e-5;
1660 const double Z_MIN = 1e-4;
1661 const double F_MIN = 1e-15;
1662 const double H_SLACK = 0.1;
1664 auto sat = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS", fluids));
1665 sat->set_mole_fractions(z);
1667 const double T_bub = sat->T();
1669 const double T_dew = sat->T();
1670 REQUIRE(T_dew > T_bub);
1672 auto liq = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS", fluids));
1673 auto vap = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS", fluids));
1674 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS", fluids));
1675 AS->set_mole_fractions(z);
1677 double H_prev = -1e100;
1678 for (
int i = 0; i < NQ; ++i) {
1679 const double alpha = (
static_cast<double>(i) + 0.5) / NQ;
1680 const double T = T_bub + (T_dew - T_bub) * alpha;
1686 const double H = AS->hmolar();
1687 CHECK(
H >= H_prev - H_SLACK);
1691 const auto x = AS->mole_fractions_liquid();
1692 const auto y = AS->mole_fractions_vapor();
1693 liq->set_mole_fractions(x);
1696 vap->set_mole_fractions(y);
1700 for (std::size_t j = 0; j < NC; ++j) {
1701 if (std::min(x[j], y[j]) < Z_MIN)
continue;
1702 const double f_liq = liq->fugacity_coefficient(j) * x[j];
1703 const double f_vap = vap->fugacity_coefficient(j) * y[j];
1704 const double f_ref = std::max(std::abs(f_liq), std::abs(f_vap));
1705 if (f_ref > F_MIN) {
1707 CHECK(std::abs(f_liq - f_vap) / f_ref < FUG_TOL);
1714TEST_CASE(
"PQ flash: 5-component two-phase consistency",
"[michelsen][flash][VLE]") {
1720 const std::string fluids =
"Nitrogen&Methane&Ethane&Butane&Pentane";
1721 const std::vector<double> z = {0.3797, 0.3225, 0.278, 0.0014, 0.0184};
1722 const double P = 3e5;
1724 const std::size_t NC = z.size();
1725 const double FUG_TOL = 1e-5;
1726 const double Z_MIN = 1e-4;
1727 const double F_MIN = 1e-15;
1729 auto sat = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS", fluids));
1730 sat->set_mole_fractions(z);
1732 const double T_bub = sat->T();
1734 const double T_dew = sat->T();
1735 REQUIRE(T_dew > T_bub);
1737 auto liq = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS", fluids));
1738 auto vap = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS", fluids));
1739 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS", fluids));
1740 AS->set_mole_fractions(z);
1742 double T_prev = -1e100;
1743 for (
int i = 0; i < NQ; ++i) {
1744 const double Q = (
static_cast<double>(i) + 0.5) / NQ;
1750 const double T = AS->T();
1755 const auto x = AS->mole_fractions_liquid();
1756 const auto y = AS->mole_fractions_vapor();
1757 liq->set_mole_fractions(x);
1760 vap->set_mole_fractions(y);
1764 for (std::size_t j = 0; j < NC; ++j) {
1765 if (std::min(x[j], y[j]) < Z_MIN)
continue;
1766 const double f_liq = liq->fugacity_coefficient(j) * x[j];
1767 const double f_vap = vap->fugacity_coefficient(j) * y[j];
1768 const double f_ref = std::max(std::abs(f_liq), std::abs(f_vap));
1769 if (f_ref > F_MIN) {
1771 CHECK(std::abs(f_liq - f_vap) / f_ref < FUG_TOL);
1785TEST_CASE(
"PT flash: wide-boiling split survives the convergence gate (GitHub #3192)",
"[michelsen][flash][VLE]") {
1787 const std::string fluids =
"Nitrogen&Methane&Ethane&Butane&Pentane";
1788 const std::vector<double> z = {0.3797, 0.3225, 0.278, 0.0014, 0.0184};
1789 const double P = 3e5;
1791 auto sat = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS", fluids));
1792 sat->set_mole_fractions(z);
1794 const double T_bub = sat->T();
1796 const double T_dew = sat->T();
1797 REQUIRE(T_dew > T_bub);
1802 for (
double frac : {0.005, 0.01, 0.02, 0.03, 0.05}) {
1803 const double T = T_bub + frac * (T_dew - T_bub);
1804 DYNAMIC_SECTION(
"frac = " << frac <<
" (T = " <<
T <<
" K)") {
1805 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS", fluids));
1806 AS->set_mole_fractions(z);
1807 REQUIRE_NOTHROW(AS->update(
PT_INPUTS, P,
T));
1809 CHECK(AS->Q() > 0.0);
1810 CHECK(AS->Q() < 1.0);
1812 const auto x = AS->mole_fractions_liquid_double();
1813 const auto y = AS->mole_fractions_vapor_double();
1815 for (std::size_t i = 0; i < z.size(); ++i)
1816 spread = std::max(spread, std::abs(x[i] - y[i]));
1817 CHECK(spread > 1e-2);
1818 CHECK(equilibrium_residual(
"HEOS", fluids, x, y,
T, P) < 1e-5);
1823TEST_CASE(
"PQ flash with built PE: N2/CH4",
"[michelsen][flash][PQ_flash][PhaseEnvelope]") {
1826 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS",
"Nitrogen&Methane"));
1827 AS->set_mole_fractions({0.5, 0.5});
1828 AS->build_phase_envelope(
"");
1829 const std::size_t npts = AS->get_phase_envelope_data().T.size();
1832 CHECK(AS->get_phase_envelope_data().built);
1835 auto AS2 = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS",
"Nitrogen&Methane"));
1836 AS2->set_mole_fractions({0.5, 0.5});
1837 REQUIRE_NOTHROW(AS2->update(
PQ_INPUTS, 1.5e5, 0.5));
1841TEST_CASE(
"PQ flash: 6-component no-throw sweep",
"[michelsen][flash][PQ_flash]") {
1846 const std::string fluids =
"Nitrogen&Methane&Ethane&Propane&Butane&Pentane";
1847 const std::vector<double> z = {0.2936, 0.2720, 0.0592, 0.2932, 0.0787, 0.0033};
1848 const double P = 3.92e5;
1851 const std::vector<std::string>
backends = {
"SRK",
"PR",
"HEOS"};
1853 DYNAMIC_SECTION(be) {
1854 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(be, fluids));
1855 AS->set_mole_fractions(z);
1856 for (
int i = 0; i < NQ; ++i) {
1857 double q =
static_cast<double>(i) / (NQ - 1);
1858 REQUIRE_NOTHROW(AS->update(
PQ_INPUTS, P, q));
1874TEST_CASE(
"DHSU_T flash: two-phase N2/O2 HEOS round-trip",
"[michelsen][dhsu_t][twophase]") {
1875 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS",
"Nitrogen&Oxygen"));
1876 AS->set_mole_fractions({0.79, 0.21});
1878 double T = AS->T(),
H = AS->hmolar(), S = AS->smolar();
1879 double U = AS->umolar(), rho = AS->rhomolar(), Q = AS->Q();
1881 SECTION(
"HmolarT recovers two-phase state") {
1882 auto AS2 = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS",
"Nitrogen&Oxygen"));
1883 AS2->set_mole_fractions({0.79, 0.21});
1885 CHECK(AS2->rhomolar() == Catch::Approx(rho).epsilon(0.01));
1886 CHECK(AS2->Q() >= 0);
1887 CHECK(AS2->Q() <= 1);
1888 CHECK(AS2->Q() == Catch::Approx(Q).margin(0.05));
1890 SECTION(
"SmolarT recovers two-phase state") {
1891 auto AS2 = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS",
"Nitrogen&Oxygen"));
1892 AS2->set_mole_fractions({0.79, 0.21});
1894 CHECK(AS2->rhomolar() == Catch::Approx(rho).epsilon(0.01));
1895 CHECK(AS2->Q() >= 0);
1896 CHECK(AS2->Q() <= 1);
1897 CHECK(AS2->Q() == Catch::Approx(Q).margin(0.05));
1903 SECTION(
"TUmolar recovers two-phase state") {
1904 auto AS2 = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS",
"Nitrogen&Oxygen"));
1905 AS2->set_mole_fractions({0.79, 0.21});
1907 CHECK(AS2->rhomolar() == Catch::Approx(rho).epsilon(0.01));
1908 CHECK(AS2->Q() >= 0);
1909 CHECK(AS2->Q() <= 1);
1910 CHECK(AS2->Q() == Catch::Approx(Q).margin(0.05));
1914TEST_CASE(
"DHSU_T flash: two-phase cubic round-trip",
"[michelsen][dhsu_t][twophase][cubic]") {
1915 const std::vector<std::string>
backends = {
"SRK",
"PR"};
1917 DYNAMIC_SECTION(be <<
" backend") {
1918 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(be,
"Nitrogen&Oxygen"));
1919 AS->set_mole_fractions({0.79, 0.21});
1921 double T = AS->T(),
H = AS->hmolar(), S = AS->smolar();
1922 double rho = AS->rhomolar(), Q = AS->Q();
1924 SECTION(
"HmolarT") {
1925 auto AS2 = std::shared_ptr<AbstractState>(AbstractState::factory(be,
"Nitrogen&Oxygen"));
1926 AS2->set_mole_fractions({0.79, 0.21});
1928 CHECK(AS2->rhomolar() == Catch::Approx(rho).epsilon(0.02));
1929 CHECK(AS2->Q() >= 0);
1930 CHECK(AS2->Q() <= 1);
1932 SECTION(
"SmolarT") {
1933 auto AS2 = std::shared_ptr<AbstractState>(AbstractState::factory(be,
"Nitrogen&Oxygen"));
1934 AS2->set_mole_fractions({0.79, 0.21});
1936 CHECK(AS2->rhomolar() == Catch::Approx(rho).epsilon(0.02));
1937 CHECK(AS2->Q() >= 0);
1938 CHECK(AS2->Q() <= 1);
1940 SECTION(
"TUmolar") {
1941 double U = AS->umolar();
1942 auto AS2 = std::shared_ptr<AbstractState>(AbstractState::factory(be,
"Nitrogen&Oxygen"));
1943 AS2->set_mole_fractions({0.79, 0.21});
1945 CHECK(AS2->rhomolar() == Catch::Approx(rho).epsilon(0.02));
1946 CHECK(AS2->Q() >= 0);
1947 CHECK(AS2->Q() <= 1);
1953TEST_CASE(
"DHSU_T flash: single-phase mixture regression",
"[michelsen][dhsu_t]") {
1956 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS",
"Nitrogen&Oxygen"));
1957 AS->set_mole_fractions({0.79, 0.21});
1960 double T = AS->T(),
H = AS->hmolar(), S = AS->smolar();
1961 double U = AS->umolar(), rho = AS->rhomolar();
1963 SECTION(
"HmolarT single-phase") {
1964 auto AS2 = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS",
"Nitrogen&Oxygen"));
1965 AS2->set_mole_fractions({0.79, 0.21});
1967 CHECK(AS2->rhomolar() == Catch::Approx(rho).epsilon(0.001));
1968 CHECK(AS2->p() == Catch::Approx(1e5).epsilon(0.001));
1970 SECTION(
"SmolarT single-phase") {
1971 auto AS2 = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS",
"Nitrogen&Oxygen"));
1972 AS2->set_mole_fractions({0.79, 0.21});
1974 CHECK(AS2->rhomolar() == Catch::Approx(rho).epsilon(0.001));
1975 CHECK(AS2->p() == Catch::Approx(1e5).epsilon(0.001));
1977 SECTION(
"DmolarT single-phase") {
1978 auto AS2 = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS",
"Nitrogen&Oxygen"));
1979 AS2->set_mole_fractions({0.79, 0.21});
1981 CHECK(AS2->hmolar() == Catch::Approx(
H).epsilon(0.001));
1982 CHECK(AS2->p() == Catch::Approx(1e5).epsilon(0.001));
1984 SECTION(
"TUmolar single-phase") {
1985 auto AS2 = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS",
"Nitrogen&Oxygen"));
1986 AS2->set_mole_fractions({0.79, 0.21});
1988 CHECK(AS2->rhomolar() == Catch::Approx(rho).epsilon(0.001));
1989 CHECK(AS2->p() == Catch::Approx(1e5).epsilon(0.001));
1993TEST_CASE(
"HSU_D flash: two-phase N2/O2 HEOS round-trip",
"[michelsen][hsu_d][twophase]") {
1994 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS",
"Nitrogen&Oxygen"));
1995 AS->set_mole_fractions({0.79, 0.21});
1997 double T = AS->T(),
H = AS->hmolar(), S = AS->smolar();
1998 double U = AS->umolar(), rho = AS->rhomolar();
2000 SECTION(
"DmolarHmolar") {
2001 auto AS2 = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS",
"Nitrogen&Oxygen"));
2002 AS2->set_mole_fractions({0.79, 0.21});
2004 CHECK(AS2->T() == Catch::Approx(
T).epsilon(0.01));
2005 CHECK(AS2->Q() >= 0);
2006 CHECK(AS2->Q() <= 1);
2008 SECTION(
"DmolarSmolar") {
2009 auto AS2 = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS",
"Nitrogen&Oxygen"));
2010 AS2->set_mole_fractions({0.79, 0.21});
2012 CHECK(AS2->T() == Catch::Approx(
T).epsilon(0.01));
2013 CHECK(AS2->Q() >= 0);
2014 CHECK(AS2->Q() <= 1);
2016 SECTION(
"DmolarUmolar") {
2017 auto AS2 = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS",
"Nitrogen&Oxygen"));
2018 AS2->set_mole_fractions({0.79, 0.21});
2020 CHECK(AS2->T() == Catch::Approx(
T).epsilon(0.01));
2021 CHECK(AS2->Q() >= 0);
2022 CHECK(AS2->Q() <= 1);
2026TEST_CASE(
"HSU_D flash: two-phase N2/O2 cubic round-trip",
"[michelsen][hsu_d][twophase][cubic]") {
2027 for (
const std::string& backend : {
"SRK",
"PR"}) {
2028 DYNAMIC_SECTION(
"Backend: " << backend) {
2029 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(backend,
"Nitrogen&Oxygen"));
2030 AS->set_mole_fractions({0.79, 0.21});
2032 double T = AS->T(),
H = AS->hmolar(), S = AS->smolar();
2033 double U = AS->umolar(), rho = AS->rhomolar();
2035 SECTION(
"DmolarHmolar") {
2036 auto AS2 = std::shared_ptr<AbstractState>(AbstractState::factory(backend,
"Nitrogen&Oxygen"));
2037 AS2->set_mole_fractions({0.79, 0.21});
2039 CHECK(AS2->T() == Catch::Approx(
T).epsilon(0.01));
2041 SECTION(
"DmolarSmolar") {
2042 auto AS2 = std::shared_ptr<AbstractState>(AbstractState::factory(backend,
"Nitrogen&Oxygen"));
2043 AS2->set_mole_fractions({0.79, 0.21});
2045 CHECK(AS2->T() == Catch::Approx(
T).epsilon(0.01));
2047 SECTION(
"DmolarUmolar") {
2048 auto AS2 = std::shared_ptr<AbstractState>(AbstractState::factory(backend,
"Nitrogen&Oxygen"));
2049 AS2->set_mole_fractions({0.79, 0.21});
2051 CHECK(AS2->T() == Catch::Approx(
T).epsilon(0.01));
2057TEST_CASE(
"HSU_D flash: single-phase N2/O2 HEOS regression",
"[michelsen][hsu_d][singlephase]") {
2058 auto AS = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS",
"Nitrogen&Oxygen"));
2059 AS->set_mole_fractions({0.79, 0.21});
2061 double T = AS->T(), P = AS->p(),
H = AS->hmolar(), S = AS->smolar();
2062 double U = AS->umolar(), rho = AS->rhomolar();
2064 SECTION(
"DmolarHmolar") {
2065 auto AS2 = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS",
"Nitrogen&Oxygen"));
2066 AS2->set_mole_fractions({0.79, 0.21});
2068 CHECK(AS2->T() == Catch::Approx(
T).epsilon(0.001));
2069 CHECK(AS2->p() == Catch::Approx(P).epsilon(0.001));
2071 SECTION(
"DmolarSmolar") {
2072 auto AS2 = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS",
"Nitrogen&Oxygen"));
2073 AS2->set_mole_fractions({0.79, 0.21});
2075 CHECK(AS2->T() == Catch::Approx(
T).epsilon(0.001));
2076 CHECK(AS2->p() == Catch::Approx(P).epsilon(0.001));
2078 SECTION(
"DmolarUmolar") {
2079 auto AS2 = std::shared_ptr<AbstractState>(AbstractState::factory(
"HEOS",
"Nitrogen&Oxygen"));
2080 AS2->set_mole_fractions({0.79, 0.21});
2082 CHECK(AS2->T() == Catch::Approx(
T).epsilon(0.001));
2083 CHECK(AS2->p() == Catch::Approx(P).epsilon(0.001));