Newer
Older
#include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp>
#include <catch2/generators/catch_generators.hpp>
#include <catch2/generators/catch_generators_adapters.hpp>
#include <catch2/generators/catch_generators_range.hpp>
#include "teqp/models/multifluid.hpp"
#include "teqp/models/multifluid_ancillaries.hpp"
#include "teqp/algorithms/critical_tracing.hpp"
#include "teqp/algorithms/VLE.hpp"
#include "teqp/cpp/teqpcpp.hpp"
#include "teqp/cpp/deriv_adapter.hpp"
#include "teqp/ideal_eosterms.hpp"
using multifluid_t = decltype(build_multifluid_model({""}, ""));
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
TEST_CASE("Test infinite dilution critical locus derivatives for multifluid", "[crit]")
{
std::string root = "../mycp";
const auto model = build_multifluid_model({ "Nitrogen", "Ethane" }, root);
using ct = CriticalTracing<decltype(model), double, Eigen::ArrayXd>;
for (int i = 0; i < 2; ++i) {
auto rhoc0 = 1/model.redfunc.vc[i];
double T0 = model.redfunc.Tc[i];
Eigen::ArrayXd rhovec0(2); rhovec0.setZero(); rhovec0[i] = rhoc0;
// Values for infinite dilution
auto infdil = ct::get_drhovec_dT_crit(model, T0, rhovec0);
auto epinfdil = ct::eigen_problem(model, T0, rhovec0);
// Just slightly not infinite dilution, values should be very similar
Eigen::ArrayXd rhovec0almost = rhovec0; rhovec0almost[1 - i] = 1e-6;
auto dil = ct::get_drhovec_dT_crit(model, T0, rhovec0almost);
auto epdil = ct::eigen_problem(model, T0, rhovec0almost);
int rr = 0;
}
}
TEST_CASE("Test infinite dilution critical locus derivatives for multifluid with both orders", "[crit]")
{
std::string root = "../mycp";
auto pure_endpoint = [&](const std::vector < std::string> &fluids, int i) {
const auto model = build_multifluid_model(fluids, root);
using ct = CriticalTracing<decltype(model), double, Eigen::ArrayXd>;
auto rhoc0 = 1 / model.redfunc.vc[i];
double T0 = model.redfunc.Tc[i];
Eigen::ArrayXd rhovec0(2); rhovec0.setZero(); rhovec0[i] = rhoc0;
// Values for infinite dilution
auto infdil = ct::get_drhovec_dT_crit(model, T0, rhovec0);
auto epinfdil = ct::eigen_problem(model, T0, rhovec0);
auto der = ct::get_derivs(model, T0, rhovec0);
using tdx = TDXDerivatives<decltype(model), double, Eigen::ArrayXd>;
auto z = (rhovec0 / rhovec0.sum()).eval();
auto alphar = model.alphar(T0, rhoc0, z);
return std::make_tuple(T0, rhoc0, alphar, infdil, epinfdil, der);
};
auto [T0, rho0, alphar0, infdil0, eig0, der0] = pure_endpoint({ "Nitrogen", "Ethane" }, 0);
auto [T1, rho1, alphar1, infdil1, eig1, der1] = pure_endpoint({ "Ethane", "Nitrogen" }, 1);
CHECK(T0 == T1);
CHECK(rho0 == rho1);
CHECK(alphar0 == alphar1);
CHECK(infdil0(1) == Approx(infdil1(0)));
CHECK(infdil0(0) == Approx(infdil1(1)));
auto [Ta, rhoa, alphara, infdila, eiga, dera] = pure_endpoint({ "Ethane", "Nitrogen" }, 0);
auto [Tb, rhob, alpharb, infdilb, eigb, derb] = pure_endpoint({ "Nitrogen", "Ethane" }, 1);
CHECK(Ta == Tb);
CHECK(rhoa == rhob);
CHECK(alphara == alpharb);
CHECK(infdila(1) == Approx(infdilb(0)));
CHECK(infdila(0) == Approx(infdilb(1)));
int rr = 0;
}
TEST_CASE("Confirm failure for missing files","[multifluid]") {
CHECK_THROWS(build_multifluid_model({ "BADFLUID" }, "IMPOSSIBLE PATH", "IMPOSSIBLE PATH.json"));
CHECK_THROWS(build_multifluid_model({ "BADFLUID" }, "IMPOSSIBLE PATH", "../mycp/dev/mixtures/mixture_binary_pairs.json"));
CHECK_THROWS(build_multifluid_model({ "Ethane" }, "IMPOSSIBLE PATH"));
}
TEST_CASE("Trace critical locus for nitrogen + ethane", "[crit],[multifluid]")
{
std::string root = "../mycp";
const auto model = build_multifluid_model({ "Nitrogen", "Ethane" }, root);
Ian Bell
committed
for (auto ifluid = 0; ifluid < 2; ++ifluid) {
double T0 = model.redfunc.Tc[ifluid];
Eigen::ArrayXd rhovec0(2); rhovec0 = 0.0; rhovec0[ifluid] = 1.0 / model.redfunc.vc[ifluid];
auto tic0 = std::chrono::steady_clock::now();
Ian Bell
committed
using ct = CriticalTracing<decltype(model), double, Eigen::ArrayXd>;
TCABOptions opt; opt.init_dt = 100; opt.integration_order = 1;
auto j = ct::trace_critical_arclength_binary(model, T0, rhovec0, filename, opt);
CHECK(j.size() > 3);
auto tic1 = std::chrono::steady_clock::now();
}
for (auto ifluid = 0; ifluid < 2; ++ifluid) {
double T0 = model.redfunc.Tc[ifluid];
Eigen::ArrayXd rhovec0(2); rhovec0 = 0.0; rhovec0[ifluid] = 1.0 / model.redfunc.vc[ifluid];
auto tic0 = std::chrono::steady_clock::now();
using ct = CriticalTracing<decltype(model), double, Eigen::ArrayXd>;
TCABOptions opt; opt.max_dt = 10000; opt.init_dt = 10; opt.abs_err = 1e-8; opt.rel_err = 1e-6; opt.small_T_count = 100;
auto j = ct::trace_critical_arclength_binary(model, T0, rhovec0, filename, opt);
CHECK(j.size() > 3);
auto tic1 = std::chrono::steady_clock::now();
}
TEST_CASE("Check that all pure fluid models can be instantiated", "[multifluid],[all]"){
SECTION("With absolute paths to json file") {
for (auto path : get_files_in_folder(root + "/dev/fluids", ".json")) {
if (path.filename().stem() == "Methanol") { continue; }
CAPTURE(path.string());
auto abspath = std::filesystem::absolute(path).string();
auto model = build_multifluid_model({ abspath }, root, root + "/dev/mixtures/mixture_binary_pairs.json");
std::valarray<double> z(0.0, 1);
model.alphar(300, 1.0, z);
counter += 1;
SECTION("With filename stems") {
for (auto path : get_files_in_folder(root + "/dev/fluids", ".json")) {
auto stem = path.filename().stem().string(); // filename without the .json
if (stem == "Methanol") { continue; }
auto model = build_multifluid_model({ stem }, root, root + "/dev/mixtures/mixture_binary_pairs.json");
std::valarray<double> z(0.0, 1);
model.alphar(300, 1.0, z);
TEST_CASE("Check that all ancillaries can be instantiated and work properly", "[multifluid],[all]") {
std::string root = "../mycp";
SECTION("With absolute paths to json file") {
int counter = 0;
for (auto path : get_files_in_folder(root + "/dev/fluids", ".json")) {
if (path.filename().stem() == "Methanol") { continue; }
CAPTURE(path.string());
auto abspath = std::filesystem::absolute(path).string();
auto model = build_multifluid_model({ abspath }, root, root + "/dev/mixtures/mixture_binary_pairs.json");
auto pure0 = nlohmann::json::parse(model.get_meta()).at("pures")[0];
// Skip pseudo-pure fluids, where ancillary checking is irrelevant
if (pure0.at("EOS")[0].at("pseudo_pure")){
counter += 1;
continue;
}
auto jancillaries = pure0.at("ANCILLARIES");
auto anc = teqp::MultiFluidVLEAncillaries(jancillaries);
double T = 0.8*anc.rhoL.T_r;
auto rhoV = anc.rhoV(T), rhoL = anc.rhoL(T);
auto rhovec = teqp::pure_VLE_T(model, T, rhoL, rhoV, 10);
CAPTURE(rhoL);
CAPTURE(rhoV);
CAPTURE(rhovec);
CHECK_THROWS(anc.rhoV(1.1*anc.rhoL.T_r));
CHECK_THROWS(anc.rhoL(1.1*anc.rhoL.T_r));
auto rhoLerr = std::abs(rhovec[0]/rhoL-1);
auto rhoVerr = std::abs(rhovec[1]/rhoV-1);
CHECK(rhoLerr < 0.02);
CHECK(rhoVerr < 0.02);
counter += 1;
}
CHECK(counter > 100);
}
}
TEST_CASE("Check that mixtures can also do absolute paths", "[multifluid],[abspath]") {
std::string root = "../mycp";
SECTION("With absolute paths to json file") {
std::vector<std::filesystem::path> paths = { root + "/dev/fluids/Methane.json", root + "/dev/fluids/Ethane.json" };
std::vector<std::string> abspaths;
for (auto p : paths) {
abspaths.emplace_back(std::filesystem::absolute(p).string());
}
auto model = build_multifluid_model(abspaths, root, root + "/dev/mixtures/mixture_binary_pairs.json");
auto model2 = build_multifluid_model(abspaths, root); // default path for BIP
Ian Bell
committed
}
TEST_CASE("Check mixing absolute and relative paths and fluid names", "[multifluid],[abspath]") {
std::string root = "../mycp";
SECTION("With correct name of fluid") {
std::vector<std::string> paths = { std::filesystem::absolute(root + "/dev/fluids/Methane.json").string(), "Ethane" };
auto model = build_multifluid_model(paths, root, root + "/dev/mixtures/mixture_binary_pairs.json");
}
SECTION("Needing a reverse lookup for one fluid") {
std::vector<std::string> paths = { std::filesystem::absolute(root + "/dev/fluids/Methane.json").string(), "PROPANE" };
auto model = build_multifluid_model(paths, root, root + "/dev/mixtures/mixture_binary_pairs.json");
}
}
TEST_CASE("Check specifying some different kinds of sources of BIP", "[multifluidBIP]") {
std::string root = "../mycp";
SECTION("Not JSON, should throw") {
std::vector<std::string> paths = { std::filesystem::absolute(root + "/dev/fluids/Nitrogen.json").string(), "Ethane" };
CHECK_THROWS(build_multifluid_model(paths, root, "I am not a JSON formatted string"));
}
SECTION("The normal approach") {
std::vector<std::string> paths = { std::filesystem::absolute(root + "/dev/fluids/Nitrogen.json").string(), "Ethane" };
auto model = build_multifluid_model(paths, root, root + "/dev/mixtures/mixture_binary_pairs.json");
}
SECTION("Sending the contents in JSON format") {
std::vector<std::string> paths = { std::filesystem::absolute(root + "/dev/fluids/Nitrogen.json").string(), "PROPANE" };
auto BIP = load_a_JSON_file(root + "/dev/mixtures/mixture_binary_pairs.json");
auto model = build_multifluid_model(paths, root, BIP.dump());
}
}
Ian Bell
committed
TEST_CASE("Check that all binary pairs specified in the binary pair file can be instantiated", "[multifluid],[binaries]") {
std::string root = "../mycp";
REQUIRE_NOTHROW(build_alias_map(root));
auto amap = build_alias_map(root);
for (auto el : load_a_JSON_file(root + "/dev/mixtures/mixture_binary_pairs.json")) {
auto is_unsupported = [](const auto& s) {
return (s == "METHANOL" || s == "R1216" || s == "C14" || s == "IOCTANE" || s == "C4F10" || s == "C5F12" || s == "C1CC6" || s == "C3CC6" || s == "CHLORINE" || s == "RE347MCC");
};
if (is_unsupported(el["Name1"]) || is_unsupported(el["Name2"])) {
continue;
}
CAPTURE(el["Name1"]);
CAPTURE(el["Name2"]);
CHECK_NOTHROW(build_multifluid_model({ amap[el["Name1"]], amap[el["Name2"]] }, root)); // default path for BIP
}
Ian Bell
committed
}
TEST_CASE("Check that all pure fluid models can be evaluated at zero density", "[multifluid],[all],[virial]") {
std::string root = "../mycp";
SECTION("With filename stems") {
for (auto path : get_files_in_folder(root + "/dev/fluids", ".json")) {
auto stem = path.filename().stem().string(); // filename without the .json
if (stem == "Methanol") { continue; }
auto model = build_multifluid_model({ stem }, root);
std::valarray<double> z(1.0, 1);
using tdx = TDXDerivatives<decltype(model), double, decltype(z) >;
auto ders = tdx::template get_Ar0n<4>(model, model.redfunc.Tc[0], 0.0, z);
CAPTURE(stem);
CHECK(std::isfinite(ders[1]));
using vd = VirialDerivatives<decltype(model),double, decltype(z)>;
auto Bn = vd::get_Bnvir<4>(model, model.redfunc.Tc[0], z);
CAPTURE(stem);
CHECK(std::isfinite(Bn[2]));
}
}
}
TEST_CASE("Check that virial coefficients can be calculated with multiple derivative methods", "[multifluid],[virial]") {
std::string root = "../mycp";
std::string stem = "Argon";
CAPTURE(stem);
auto model = build_multifluid_model({ stem }, root);
std::valarray<double> z(1.0, 1);
using vd = VirialDerivatives<decltype(model), double, decltype(z)>;
auto BnAD = vd::get_Bnvir<4, ADBackends::autodiff>(model, 298.15, z);
auto Bnmcx = vd::get_Bnvir<4, ADBackends::multicomplex>(model, 298.15, z);
CHECK(BnAD[2] == Approx(Bnmcx[2]));
CHECK(BnAD[3] == Approx(Bnmcx[3]));
CHECK(BnAD[4] == Approx(Bnmcx[4]));
auto derBnAD100 = vd::get_dmBnvirdTm<2, 1, ADBackends::autodiff>(model, 100.0, z);
auto derBnAD = vd::get_dmBnvirdTm<2, 1, ADBackends::autodiff>(model, 298.15, z);
auto derBnMCX = vd::get_dmBnvirdTm<2, 1, ADBackends::multicomplex>(model, 298.15, z);
CHECK(derBnAD == Approx(derBnMCX));
TEST_CASE("dpsat/dTsat", "[dpdTsat]") {
std::string root = "../mycp";
const auto model = build_multifluid_model({ "Methane", "Ethane" }, root);
using id = IsochoricDerivatives<decltype(model)>;
double T = 200;
auto rhovecL = (Eigen::ArrayXd(2) << 5431.76157173312, 12674.110334043948).finished();
auto rhovecV = (Eigen::ArrayXd(2) << 1035.298519871195, 162.03291757734976).finished();
// Concentration derivatives w.r.t. T along the isopleth
auto [drhovecdTL, drhovecdTV] = get_drhovecdT_xsat(model, T, rhovecL, rhovecV);
auto dpdT = get_dpsat_dTsat_isopleth(model, T, rhovecL, rhovecV);
CHECK(dpdT == Approx(39348.33949198946).margin(0.01));
}
TEST_CASE("Trace a VLE isotherm for CO2 + water", "[isothermCO2water]") {
std::string root = "../mycp";
const auto model = build_multifluid_model({ "CarbonDioxide", "Water" }, root);
using id = IsochoricDerivatives<decltype(model)>;
double T = 308.15;
auto rhovecL = (Eigen::ArrayXd(2) << 0.0, 55174.92375117).finished();
auto rhovecV = (Eigen::ArrayXd(2) << 0.0, 2.20225704).finished();
auto o = trace_VLE_isotherm_binary(model, T, rhovecL, rhovecV);
}
TEST_CASE("Trace a VLE isotherm for acetone + water", "[isothermacetonebenzene]") {
std::string root = "../mycp";
const auto model = build_multifluid_model({ "Acetone", "Benzene" }, root);
using id = IsochoricDerivatives<decltype(model)>;
double T = 348.05;
auto rhovecL = (Eigen::ArrayXd(2) << 12502.86504072, 0.0).finished();
auto rhovecV = (Eigen::ArrayXd(2) << 69.20719534, 0.0).finished();
auto o = trace_VLE_isotherm_binary(model, T, rhovecL, rhovecV);
TEST_CASE("Calculate water at critical point", "[WATERcrit]") {
std::string root = "../mycp";
const auto model = build_multifluid_model({ "Water" }, root);
using tdx = TDXDerivatives<decltype(model)>;
auto Tc = model.redfunc.Tc[0];
auto rhoc = 1/model.redfunc.vc[0];
auto z = (Eigen::ArrayXd(1) << 1.0).finished();
auto a1 = tdx::get_Ar0n<1>(model, Tc, rhoc, z);
CHECK(std::isfinite(a1[1]));
auto a2 = tdx::get_Ar0n<2>(model, Tc, rhoc, z);
auto a3 = tdx::get_Ar0n<3>(model, Tc, rhoc, z);
auto a4 = tdx::get_Ar0n<3>(model, Tc, rhoc, z);
CHECK(a3[1] == a1[1]);
auto R = model.R(z);
auto dpdrho = R*Tc*(1 + 2*a4[1] + a4[2]);
auto d2pdrho2 = R*Tc/(rhoc)*(2*a4[1] + 4*a4[2] + a4[3]);
CHECK(dpdrho == Approx(0).margin(1e-9));
CHECK(d2pdrho2 == Approx(0).margin(1e-9));
}
TEST_CASE("Calculate partial molar volume for a CO2 containing mixture", "[partial_molar_volume]") {
std::string root = "../mycp";
const auto model = build_multifluid_model({ "CarbonDioxide", "Heptane" }, root);
using id = IsochoricDerivatives<decltype(model), double, Eigen::ArrayXd>;
double T = 343.0;
Eigen::ArrayXd rhovec = (Eigen::ArrayXd(2) << 0.99999, 1.0-0.99999).finished();
rhovec *= 6690.19673875373;
std::valarray<double> expected = { 0.000149479684800994, -0.000575458122621522 };
auto der = id::get_partial_molar_volumes(model, T, rhovec);
for (auto i = 0; i < expected.size(); ++i){
TEST_CASE("Check that all pure fluid ideal-gas terms can be converted", "[multifluid],[all],[alphaig]") {
std::string root = "../mycp";
auto paths = get_files_in_folder(root + "/dev/fluids", ".json");
auto p = GENERATE_REF(from_range(paths));
CHECK(std::filesystem::is_regular_file(p));
CAPTURE(p);
// Check can be loaded from both path and string contents
auto jig = convert_CoolProp_idealgas(p.string(), 0 /* index of EOS */);
auto jig2 = convert_CoolProp_idealgas(load_a_JSON_file(p.string()).dump(), 0 /* index of EOS */);
// Convert to json array
nlohmann::json jaig = nlohmann::json::array(); jaig.push_back(jig);
CHECK(jaig.is_array());
// std::cout << jaig.dump() << std::endl;
// Check that converted structures can be loaded
auto aig = IdealHelmholtz(jaig);
}
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
TEST_CASE("Check that BIP can be set in a string", "[multifluida]") {
std::string root = "../mycp";
double T = 300, rhomolar = 300;
auto z = (Eigen::ArrayXd(2) << 0.4, 0.6).finished();
auto def = build_multifluid_model({"Nitrogen","Ethane"}, root); // default parameters
CHECK(TDXDerivatives<decltype(def)>::get_Ar01(def, T, rhomolar, z) == Approx(-0.026028104905899584));
std::string s = R"([{"BibTeX": "Kunz-JCED-2012", "CAS1": "7727-37-9", "CAS2": "74-84-0", "F": 1.0, "Name1": "Nitrogen", "Name2": "Ethane", "betaT": 1.01774814228, "betaV": 0.978880168, "function": "Nitrogen-Ethane", "gammaT": 1.0877732316831683, "gammaV": 1.042352891}])";
nlohmann::json model = {
{"components",{"Nitrogen","Ethane"}},
{"root", root},
{"BIP", s},
{"departure", ""}
};
nlohmann::json j = {
{"kind", "multifluid"},
{"model", model}
};
auto model_ = cppinterface::make_model(j);
CHECK(model_->get_Ar01(T, rhomolar, z) != Approx(-0.026028104905899584));
}
TEST_CASE("Check ammonia+argon", "[multifluidArNH3]") {
std::string root = "../mycp";
// Check that default model (no departure function) prints the right
auto def = build_multifluid_model({"AMMONIA","ARGON"}, root); // default parameters
nlohmann::json mixdef = nlohmann::json::parse(def.get_meta())["mix"];
// std::cout << mix.dump(1) << std::endl;
CHECK(!mixdef.empty());
CAPTURE(mixdef.dump(1));
std::string sBIP = R"([ {"BibTeX": "?", "CAS1": "7440-37-1", "CAS2": "7664-41-7", "F": 1.0, "Name1": "ARGON", "Name2": "AMMONIA", "betaT": 1.146326, "betaV": 0.756526, "function": "BAA", "gammaT": 0.998353, "gammaV": 1.041113}])";
std::string sdep = R"([{"BibTeX": "??", "Name": "BAA", "Npower": 1, "aliases": [], "beta": [0.0, 0.6, 0.5], "d": [3.0, 1.0, 1.0], "epsilon": [0.0, 0.31, 0.39], "eta": [0.0, 1.3, 1.5], "gamma": [0.0, 0.9, 1.5], "l": [1.0, 0.0, 0.0], "n": [0.02350785, -1.913776, 1.624062], "t": [2.3, 1.65, 0.42], "type": "Gaussian+Exponential"}])";
nlohmann::json jmodel = {
{"components", {"AMMONIA", "ARGON"}},
{"root", root},
{"BIP", sBIP},
{"departure", sdep}
};
nlohmann::json j = {
{"kind", "multifluid"},
{"model", jmodel}
};
auto model_ = cppinterface::make_model(j);
double T = 293.15, rhomolar = 40000;
auto z = (Eigen::ArrayXd(2) << 0.95, 0.05).finished();
double p_MPa = (model_->get_pr(T, rhomolar*z) + rhomolar*model_->get_R(z)*T)/1e6;
const auto& mref = teqp::cppinterface::adapter::get_model_cref<multifluid_t>(model_.get());
nlohmann::json mix = nlohmann::json::parse(mref.get_meta())["mix"];
// std::cout << mix.dump(1) << std::endl;
CHECK(!mix.empty());
CAPTURE(mix.dump(1));
CHECK(p_MPa == Approx(129.07019029846455).margin(1e-3));
TEST_CASE("Check pure fluid throws with composition array of wrong length", "[virial]") {
std::string root = "../mycp";
const auto model = build_multifluid_model({ "CarbonDioxide" }, root);
double T = 300;
auto z = (Eigen::ArrayXd(2) << 0.3, 0.9).finished();
using vir = VirialDerivatives<decltype(model)>;
CHECK_THROWS(vir::get_dmBnvirdTm<2,1>(model, T, z));
CHECK_THROWS(vir::get_Bnvir<2>(model, T, z));
}