diff --git a/include/teqp/cpp/teqpcpp.hpp b/include/teqp/cpp/teqpcpp.hpp index ab6ddd5a483dde8d0a9d6eb253a533a89e6ce809..7ce5bee4476ad924dbcc4d83ede38cf0d4b4d5bb 100644 --- a/include/teqp/cpp/teqpcpp.hpp +++ b/include/teqp/cpp/teqpcpp.hpp @@ -121,7 +121,7 @@ namespace teqp { virtual EArray33d get_deriv_mat2(const double T, double rho, const EArrayd& z ) const = 0; - virtual std::tuple<double, double> solve_pure_critical(const double T, const double rho, const std::optional<nlohmann::json>&) const = 0; + virtual std::tuple<double, double> solve_pure_critical(const double T, const double rho, const std::optional<nlohmann::json>& = std::nullopt) const = 0; virtual std::tuple<double, double> extrapolate_from_critical(const double Tc, const double rhoc, const double Tgiven) const = 0; virtual std::tuple<EArrayd, EMatrixd> get_pure_critical_conditions_Jacobian(const double T, const double rho, int alternative_pure_index=-1, int alternative_length=2) const = 0; virtual EArray2 pure_VLE_T(const double T, const double rhoL, const double rhoV, int maxiter) const = 0; diff --git a/include/teqp/json_builder.hpp b/include/teqp/json_builder.hpp index f7026ec5498311533d0ec7ba01855506569b3d10..4b795d2f76e74adc4379d9ac75cd736d499be4a1 100644 --- a/include/teqp/json_builder.hpp +++ b/include/teqp/json_builder.hpp @@ -11,34 +11,6 @@ namespace teqp { inline AllowedModels build_model(const nlohmann::json& json) { - - auto build_square_matrix = [](const nlohmann::json& j){ - if (j.is_null() || (j.is_array() && j.size() == 0)){ - return Eigen::ArrayXXd(0, 0); - } - try{ - const std::valarray<std::valarray<double>> m = j; - // First assume that the matrix is square, resize - Eigen::ArrayXXd mat(m.size(), m.size()); - if (m.size() == 0){ - return mat; - } - // Then copy elements over - for (auto i = 0; i < m.size(); ++i){ - auto row = m[i]; - if (row.size() != mat.rows()){ - throw std::invalid_argument("provided matrix is not square"); - } - for (auto j = 0; j < row.size(); ++j){ - mat(i, j) = row[j]; - } - } - return mat; - } - catch(const nlohmann::json::exception&){ - throw teqp::InvalidArgument("Unable to convert this kmat to a 2x2 matrix of doubles:" + j.dump(2)); - } - }; // Extract the name of the model and the model parameters std::string kind = json.at("kind"); @@ -112,48 +84,7 @@ namespace teqp { } } else if (kind == "SAFT-VR-Mie") { - using namespace SAFTVRMie; - std::optional<Eigen::ArrayXXd> kmat; - if (spec.contains("kmat") && spec.at("kmat").is_array() && spec.at("kmat").size() > 0){ - kmat = build_square_matrix(spec["kmat"]); - } - - if (spec.contains("names")){ - std::vector<std::string> names = spec["names"]; - if (kmat && kmat.value().rows() != names.size()){ - throw teqp::InvalidArgument("Provided length of names of " + std::to_string(names.size()) + " does not match the dimension of the kmat of " + std::to_string(kmat.value().rows())); - } - return SAFTVRMieMixture(names, kmat); - } - else if (spec.contains("coeffs")){ - std::vector<SAFTVRMieCoeffs> coeffs; - for (auto j : spec["coeffs"]) { - SAFTVRMieCoeffs c; - c.name = j.at("name"); - c.m = j.at("m"); - c.sigma_m = (j.contains("sigma_m")) ? j.at("sigma_m").get<double>() : j.at("sigma_Angstrom").get<double>()/1e10; - c.epsilon_over_k = j.at("epsilon_over_k"); - c.lambda_r = j.at("lambda_r"); - c.lambda_a = j.at("lambda_a"); - c.BibTeXKey = j.at("BibTeXKey"); - if (j.contains("(mu^*)^2") && j.contains("nmu")){ - c.mustar2 = j.at("(mu^*)^2"); - c.nmu = j.at("nmu"); - } - if (j.contains("(Q^*)^2") && j.contains("nQ")){ - c.Qstar2 = j.at("(Q^*)^2"); - c.nQ = j.at("nQ"); - } - coeffs.push_back(c); - } - if (kmat && kmat.value().rows() != coeffs.size()){ - throw teqp::InvalidArgument("Provided length of coeffs of " + std::to_string(coeffs.size()) + " does not match the dimension of the kmat of " + std::to_string(kmat.value().rows())); - } - return SAFTVRMieMixture(coeffs, kmat); - } - else{ - throw std::invalid_argument("you must provide names or coeffs, but not both"); - } + return SAFTVRMie::SAFTVRMiefactory(spec); } else if (kind == "multifluid") { return multifluidfactory(spec); diff --git a/include/teqp/json_tools.hpp b/include/teqp/json_tools.hpp index 2974feab6dc88302bb8fa9a8ae5530785014fa3d..952b3edb9dadc34a8f4e9ab4cd6e0c84cf37f7cd 100644 --- a/include/teqp/json_tools.hpp +++ b/include/teqp/json_tools.hpp @@ -4,6 +4,7 @@ #include <set> #include <filesystem> #include <fstream> +#include "teqp/exceptions.hpp" namespace teqp{ @@ -29,4 +30,32 @@ namespace teqp{ for (auto k : ks) { lengths.insert(j.at(k).size()); } return lengths.size() == 1; } + + auto build_square_matrix = [](const nlohmann::json& j){ + if (j.is_null() || (j.is_array() && j.size() == 0)){ + return Eigen::ArrayXXd(0, 0); + } + try{ + const std::valarray<std::valarray<double>> m = j; + // First assume that the matrix is square, resize + Eigen::ArrayXXd mat(m.size(), m.size()); + if (m.size() == 0){ + return mat; + } + // Then copy elements over + for (auto i = 0; i < m.size(); ++i){ + auto row = m[i]; + if (row.size() != mat.rows()){ + throw std::invalid_argument("provided matrix is not square"); + } + for (auto j = 0; j < row.size(); ++j){ + mat(i, j) = row[j]; + } + } + return mat; + } + catch(const nlohmann::json::exception&){ + throw teqp::InvalidArgument("Unable to convert this kmat to a 2x2 matrix of doubles:" + j.dump(2)); + } + }; } diff --git a/include/teqp/models/saft/polar_terms.hpp b/include/teqp/models/saft/polar_terms.hpp index c558a7aebf2ac425ea1097d7a07a2c6b0332b4dd..4cd3d7d2909f501eeff5c735831927233346d1a7 100644 --- a/include/teqp/models/saft/polar_terms.hpp +++ b/include/teqp/models/saft/polar_terms.hpp @@ -379,7 +379,11 @@ public: const auto& sigma = sigma_m; // concision const auto N = mole_fractions.size(); - std::common_type_t<TTYPE, RhoType, decltype(mole_fractions[0])> summer112 = 0.0, summer123 = 0.0, summer224 = 0.0; + std::common_type_t<TTYPE, RhoType, decltype(mole_fractions[0])> alpha2_112 = 0.0, alpha2_123 = 0.0, alpha2_224 = 0.0; + + const auto factor_112 = forceeval(-2.0*PI_*rhoN/3.0); //*POW2(4*PI_*epsilon_0) + const auto factor_123 = forceeval(-PI_*rhoN/3.0); + const auto factor_224 = forceeval(-14.0*PI_*rhoN/5.0); for (auto i = 0; i < N; ++i){ for (auto j = 0; j < N; ++j){ @@ -391,16 +395,12 @@ public: auto leading = x[i]*x[j]/(Tstari*Tstarj); // common for all alpha_2 terms auto Tstarij = forceeval(T/epskij); - summer112 += leading*POW3(sigma[i]*sigma[j])/POW3(sigmaij)*mubar2[i]*mubar2[j]*J6.get_J(Tstarij, rhostar); - summer123 += leading*POW3(sigma[i])*POW5(sigma[j])/POW5(sigmaij)*mubar2[i]*Qbar2[j]*J8.get_J(Tstarij, rhostar); - summer224 += leading*POW5(sigma[i]*sigma[j])/POW7(sigmaij)*Qbar2[i]*Qbar2[j]*J10.get_J(Tstarij, rhostar); + alpha2_112 += factor_112*leading*POW3(sigma[i]*sigma[j])/POW3(sigmaij)*mubar2[i]*mubar2[j]*J6.get_J(Tstarij, rhostar); + alpha2_123 += factor_123*leading*POW3(sigma[i])*POW5(sigma[j])/POW5(sigmaij)*mubar2[i]*Qbar2[j]*J8.get_J(Tstarij, rhostar); + alpha2_224 += factor_224*leading*POW5(sigma[i]*sigma[j])/POW7(sigmaij)*Qbar2[i]*Qbar2[j]*J10.get_J(Tstarij, rhostar); } } - auto alpha2_112 = -2.0*PI_*rhoN*POW2(4*PI_*epsilon_0)/3.0*summer112; - auto alpha2_123 = -PI_*rhoN*POW2(4*PI_*epsilon_0)/3.0*summer123; - auto alpha2_224 = -14.0*PI_*rhoN*POW2(4*PI_*epsilon_0)/5.0*summer224; - return forceeval(alpha2_112 + 2.0*alpha2_123 + alpha2_224); } @@ -440,31 +440,43 @@ public: auto sigmaik = (sigma[i]+sigma[k])/2; auto sigmajk = (sigma[j]+sigma[k])/2; auto leadingijk = x[i]*x[j]*x[k]/(Tstari*Tstarj*Tstark); + + auto get_Kijk = [&](const auto& Kint){ + return pow(Kint.get_K(Tstarij, rhostar)*Kint.get_K(Tstarik, rhostar)*Kint.get_K(Tstarjk, rhostar), 1.0/3.0); + }; - auto K222333 = pow(K222_333.get_K(Tstarij, rhostar)*K222_333.get_K(Tstarik, rhostar)*K222_333.get_K(Tstarjk, rhostar), 1.0/3.0); - summerB_112_112_112 += leadingijk*POW3(sigma[i]*sigma[j]*sigma[k])/(sigmaij*sigmaik*sigmajk)*mubar2[i]*mubar2[j]*mubar2[k]*K222333; - auto K233344 = pow(K233_344.get_K(Tstarij, rhostar)*K233_344.get_K(Tstarik, rhostar)*K233_344.get_K(Tstarjk, rhostar), 1.0/3.0); - summerB_112_123_123 += leadingijk*POW3(sigma[i]*sigma[j])*POW5(sigma[k])/(sigmaij*POW2(sigmaik*sigmajk))*mubar2[i]*mubar2[j]*Qbar2[k]*K233344; - auto K334445 = pow(K334_445.get_K(Tstarij, rhostar)*K334_445.get_K(Tstarik, rhostar)*K334_445.get_K(Tstarjk, rhostar), 1.0/3.0); - summerB_123_123_224 += leadingijk*POW3(sigma[i])*POW5(sigma[j]*sigma[k])/(POW2(sigmaij*sigmaik)*POW3(sigmajk))*mubar2[i]*Qbar2[j]*Qbar2[k]*K334445; - auto K444555 = pow(K444_555.get_K(Tstarij, rhostar)*K444_555.get_K(Tstarik, rhostar)*K444_555.get_K(Tstarjk, rhostar), 1.0/3.0); - summerB_224_224_224 += leadingijk*POW5(sigma[i]*sigma[j]*sigma[k])/(POW3(sigmaij*sigmaik*sigmajk))*Qbar2[i]*Qbar2[j]*Qbar2[k]*K444555; + if (std::abs(mubar2[i]*mubar2[j]*mubar2[k]) > 0){ + auto K222333 = get_Kijk(K222_333); + summerB_112_112_112 += leadingijk*POW3(sigma[i]*sigma[j]*sigma[k])/(sigmaij*sigmaik*sigmajk)*mubar2[i]*mubar2[j]*mubar2[k]*K222333; + } + if (std::abs(mubar2[i]*mubar2[j]*Qbar2[k]) > 0){ + auto K233344 = get_Kijk(K233_344); + summerB_112_123_123 += leadingijk*POW3(sigma[i]*sigma[j])*POW5(sigma[k])/(sigmaij*POW2(sigmaik*sigmajk))*mubar2[i]*mubar2[j]*Qbar2[k]*K233344; + } + if (std::abs(mubar2[i]*Qbar2[j]*Qbar2[k]) > 0){ + auto K334445 = get_Kijk(K334_445); + summerB_123_123_224 += leadingijk*POW3(sigma[i])*POW5(sigma[j]*sigma[k])/(POW2(sigmaij*sigmaik)*POW3(sigmajk))*mubar2[i]*Qbar2[j]*Qbar2[k]*K334445; + } + if (std::abs(Qbar2[i]*Qbar2[j]*Qbar2[k]) > 0){ + auto K444555 = get_Kijk(K444_555); + summerB_224_224_224 += leadingijk*POW5(sigma[i]*sigma[j]*sigma[k])/(POW3(sigmaij*sigmaik*sigmajk))*Qbar2[i]*Qbar2[j]*Qbar2[k]*K444555; + } } } } - auto alpha3A_112_112_224 = 8.0*PI_*rhoN*POW3(4*PI_*epsilon_0)/25.0*summerA_112_112_224; - auto alpha3A_112_123_213 = 8.0*PI_*rhoN*POW3(4*PI_*epsilon_0)/75.0*summerA_112_123_213; - auto alpha3A_123_123_224 = 8.0*PI_*rhoN*POW3(4*PI_*epsilon_0)/35.0*summerA_123_123_224; - auto alpha3A_224_224_224 = 144.0*PI_*rhoN*POW3(4*PI_*epsilon_0)/245.0*summerA_224_224_224; + auto alpha3A_112_112_224 = 8.0*PI_*rhoN/25.0*summerA_112_112_224; + auto alpha3A_112_123_213 = 8.0*PI_*rhoN/75.0*summerA_112_123_213; + auto alpha3A_123_123_224 = 8.0*PI_*rhoN/35.0*summerA_123_123_224; + auto alpha3A_224_224_224 = 144.0*PI_*rhoN/245.0*summerA_224_224_224; - auto alpha3A = 3.0*alpha3A_112_112_224 + 6.0*alpha3A_112_123_213 + 6.0*alpha3A_123_123_224 + alpha3A_224_224_224; + auto alpha3A = forceeval(3.0*alpha3A_112_112_224 + 6.0*alpha3A_112_123_213 + 6.0*alpha3A_123_123_224 + alpha3A_224_224_224); - auto alpha3B_112_112_112 = 32.0*POW3(PI_)*POW2(rhoN)*POW3(4*PI_*epsilon_0)/135.0*sqrt(14*PI_/5.0)*summerB_112_112_112; - auto alpha3B_112_123_123 = 64.0*POW3(PI_)*POW2(rhoN)*POW3(4*PI_*epsilon_0)/315.0*sqrt(3.0*PI_)*summerB_112_123_123; - auto alpha3B_123_123_224 = -32.0*POW3(PI_)*POW2(rhoN)*POW3(4*PI_*epsilon_0)/45.0*sqrt(22.0*PI_/63.0)*summerB_123_123_224; - auto alpha3B_224_224_224 = 32.0*POW3(PI_)*POW2(rhoN)*POW3(4*PI_*epsilon_0)/2025.0*sqrt(2002.0*PI_)*summerB_224_224_224; + auto alpha3B_112_112_112 = 32.0*POW3(PI_)*POW2(rhoN)/135.0*sqrt(14*PI_/5.0)*summerB_112_112_112; + auto alpha3B_112_123_123 = 64.0*POW3(PI_)*POW2(rhoN)/315.0*sqrt(3.0*PI_)*summerB_112_123_123; + auto alpha3B_123_123_224 = -32.0*POW3(PI_)*POW2(rhoN)/45.0*sqrt(22.0*PI_/63.0)*summerB_123_123_224; + auto alpha3B_224_224_224 = 32.0*POW3(PI_)*POW2(rhoN)/2025.0*sqrt(2002.0*PI_)*summerB_224_224_224; - auto alpha3B = alpha3B_112_112_112 + 3.0*alpha3B_112_123_123 + 3.0*alpha3B_123_123_224 + alpha3B_224_224_224; + auto alpha3B = forceeval(alpha3B_112_112_112 + 3.0*alpha3B_112_123_123 + 3.0*alpha3B_123_123_224 + alpha3B_224_224_224); return forceeval(alpha3A + alpha3B); } @@ -503,7 +515,7 @@ public: } }; -/// The variant including the multipolar terms that can be provided +/// The variant containing the multipolar types that can be provided using multipolar_contributions_variant = std::variant< MultipolarContributionGrossVrabec, MultipolarContributionGubbinsTwu<LuckasJIntegral, LuckasKIntegral>, diff --git a/include/teqp/models/saftvrmie.hpp b/include/teqp/models/saftvrmie.hpp index aa9beb5c40788bda5f67b9fcf0aa19ef3d69e167..d77c6ca61548601cfec79944c973219b483c2e48 100644 --- a/include/teqp/models/saftvrmie.hpp +++ b/include/teqp/models/saftvrmie.hpp @@ -8,6 +8,7 @@ #include "nlohmann/json.hpp" #include "teqp/types.hpp" +#include "teqp/json_tools.hpp" #include "teqp/exceptions.hpp" #include "teqp/constants.hpp" #include "teqp/math/quadrature.hpp" @@ -708,9 +709,9 @@ private: std::vector<std::string> names; const SAFTVRMieChainContributionTerms terms; - std::optional<SAFTpolar::multipolar_contributions_variant> polar; // Can be present or not + const std::optional<SAFTpolar::multipolar_contributions_variant> polar; // Can be present or not - void check_kmat(const Eigen::ArrayXXd& kmat, std::size_t N) { + static void check_kmat(const Eigen::ArrayXXd& kmat, std::size_t N) { if (kmat.size() == 0){ return; } @@ -721,11 +722,12 @@ private: throw teqp::InvalidArgument("kmat needs to be a square matrix the same size as the number of components"); } }; - auto get_coeffs_from_names(const std::vector<std::string> &names){ + static auto get_coeffs_from_names(const std::vector<std::string> &names){ SAFTVRMieLibrary library; return library.get_coeffs(names); } - auto build_chain(const std::vector<SAFTVRMieCoeffs> &coeffs, const std::optional<Eigen::ArrayXXd>& kmat){ +public: + static auto build_chain(const std::vector<SAFTVRMieCoeffs> &coeffs, const std::optional<Eigen::ArrayXXd>& kmat){ if (kmat){ check_kmat(kmat.value(), coeffs.size()); } @@ -748,7 +750,6 @@ private: return SAFTVRMieChainContributionTerms(m, epsilon_over_k, sigma_m, lambda_r, lambda_a, std::move(mat)); } } - auto build_polar(const std::vector<SAFTVRMieCoeffs> &coeffs) -> decltype(this->polar){ Eigen::ArrayXd mustar2(coeffs.size()), nmu(coeffs.size()), Qstar2(coeffs.size()), nQ(coeffs.size()); auto i = 0; @@ -773,10 +774,18 @@ private: public: SAFTVRMieMixture(const std::vector<std::string> &names, const std::optional<Eigen::ArrayXXd>& kmat = std::nullopt) : SAFTVRMieMixture(get_coeffs_from_names(names), kmat){}; SAFTVRMieMixture(const std::vector<SAFTVRMieCoeffs> &coeffs, const std::optional<Eigen::ArrayXXd> &kmat = std::nullopt) : terms(build_chain(coeffs, kmat)), polar(build_polar(coeffs)) {}; + SAFTVRMieMixture(SAFTVRMieChainContributionTerms&& terms, std::optional<SAFTpolar::multipolar_contributions_variant> &&polar = std::nullopt) : terms(std::move(terms)), polar(std::move(polar)) {}; + // PCSAFTMixture( const PCSAFTMixture& ) = delete; // non construction-copyable SAFTVRMieMixture& operator=( const SAFTVRMieMixture& ) = delete; // non copyable + auto chain_factory(const std::vector<SAFTVRMieCoeffs> &coeffs, const std::optional<Eigen::ArrayXXd>& kmat){ + SAFTVRMieMixture::build_chain(coeffs, kmat); + } + + const auto& get_polar(){ return polar; } + const auto& get_terms() const { return terms; } auto get_core_calcs(double T, double rhomolar, const Eigen::ArrayXd& mole_fractions) const { auto val = terms.get_core_calcs(T, rhomolar, mole_fractions); @@ -845,7 +854,9 @@ public: auto visitor = [&](const auto& contrib){ if constexpr(std::decay_t<decltype(contrib)>::arg_spec == mas::TK_rhoNA3_packingfraction_molefractions){ auto rho_A3 = forceeval(rhomolar*N_A*1e-30); - alphar += contrib.eval(T, rho_A3, vals.zeta[3], mole_fractions).alpha; + auto packing_fraction = vals.zeta[3]; + auto alpha = contrib.eval(T, rho_A3, packing_fraction, mole_fractions).alpha; + alphar += alpha; } else if constexpr(std::decay_t<decltype(contrib)>::arg_spec == mas::TK_rhoNm3_molefractions){ auto rhoN_m3 = forceeval(rhomolar*N_A); @@ -861,6 +872,145 @@ public: return forceeval(alphar); } }; + +static auto SAFTVRMiefactory(const nlohmann::json & spec){ + + std::optional<Eigen::ArrayXXd> kmat; + if (spec.contains("kmat") && spec.at("kmat").is_array() && spec.at("kmat").size() > 0){ + kmat = build_square_matrix(spec["kmat"]); + } + + if (spec.contains("names")){ + std::vector<std::string> names = spec["names"]; + if (kmat && kmat.value().rows() != names.size()){ + throw teqp::InvalidArgument("Provided length of names of " + std::to_string(names.size()) + " does not match the dimension of the kmat of " + std::to_string(kmat.value().rows())); + } + return SAFTVRMieMixture(names, kmat); + } + else if (spec.contains("coeffs")){ + bool something_polar = false; + std::vector<SAFTVRMieCoeffs> coeffs; + for (auto j : spec["coeffs"]) { + SAFTVRMieCoeffs c; + c.name = j.at("name"); + c.m = j.at("m"); + c.sigma_m = (j.contains("sigma_m")) ? j.at("sigma_m").get<double>() : j.at("sigma_Angstrom").get<double>()/1e10; + c.epsilon_over_k = j.at("epsilon_over_k"); + c.lambda_r = j.at("lambda_r"); + c.lambda_a = j.at("lambda_a"); + c.BibTeXKey = j.at("BibTeXKey"); + + // These are legacy definitions of the polar moments + if (j.contains("(mu^*)^2") && j.contains("nmu")){ + c.mustar2 = j.at("(mu^*)^2"); + c.nmu = j.at("nmu"); + something_polar = true; + } + if (j.contains("(Q^*)^2") && j.contains("nQ")){ + c.Qstar2 = j.at("(Q^*)^2"); + c.nQ = j.at("nQ"); + something_polar = true; + } + if (j.contains("Q_Cm2") || j.contains("Q_DA") || j.contains("mu_Cm") || j.contains("mu_D")){ + something_polar = true; + } + coeffs.push_back(c); + } + if (kmat && kmat.value().rows() != coeffs.size()){ + throw teqp::InvalidArgument("Provided length of coeffs of " + std::to_string(coeffs.size()) + " does not match the dimension of the kmat of " + std::to_string(kmat.value().rows())); + } + + if (!something_polar){ + // Nonpolar, just m, epsilon, sigma and possibly a kmat matrix with kij coefficients + return SAFTVRMieMixture(SAFTVRMieMixture::build_chain(coeffs, kmat)); + } + else{ + // Polar term is also provided, along with the chain terms + std::string polar_model = "GrossVrabec"; // This is the default, as it was the first one implemented + if (spec.contains("polar_model")){ + polar_model = spec["polar_model"]; + } + + // Go back and extract the dipolar and quadrupolar terms from + // the JSON, in base SI units + const double D_to_Cm = 3.33564e-30; // C m/D + const double mustar2factor = 1.0/(4*static_cast<double>(EIGEN_PI)*8.8541878128e-12*1.380649e-23); // factor=1/(4*pi*epsilon_0*k_B), such that (mu^*)^2 := factor*mu[Cm]^2/((epsilon/kB)[K]*sigma[m]^3) + const double Qstar2factor = 1.0/(4*static_cast<double>(EIGEN_PI)*8.8541878128e-12*1.380649e-23); // same as mustar2factor + auto N = coeffs.size(); + Eigen::ArrayXd ms(N), epsks(N), sigma_ms(N), mu_Cm(N), Q_Cm2(N), nQ(N), nmu(N); + Eigen::Index i = 0; + for (auto j : spec["coeffs"]) { + double m = j.at("m"); + double sigma_m = (j.contains("sigma_m")) ? j.at("sigma_m").get<double>() : j.at("sigma_Angstrom").get<double>()/1e10; + double epsilon_over_k = j.at("epsilon_over_k"); + auto get_dipole_Cm = [&]() -> double { + if (j.contains("(mu^*)^2") && j.contains("nmu")){ + // Terms defined like in Gross&Vrabec; backwards-compatibility + double mustar2 = j.at("(mu^*)^2"); + return sqrt(mustar2*(m*epsilon_over_k*pow(sigma_m, 3))/mustar2factor); + } + else if (j.contains("mu_Cm")){ + return j.at("mu_Cm"); + } + else if (j.contains("mu_D")){ + return j.at("mu_D").get<double>()*D_to_Cm; + } + else{ + return 0.0; + } + }; + auto get_quadrupole_Cm2 = [&]() -> double{ + if (j.contains("(Q^*)^2") && j.contains("nQ")){ + // Terms defined like in Gross&Vrabec; backwards-compatibility + double Qstar2 = j.at("(Q^*)^2"); + return sqrt(Qstar2*(m*epsilon_over_k*pow(sigma_m, 5))/Qstar2factor); + } + else if (j.contains("Q_Cm2")){ + return j.at("Q_Cm2"); + } + else if (j.contains("Q_DA")){ + return j.at("Q_Cm2").get<double>()*D_to_Cm*1e10; + } + else{ + return 0.0; + } + }; + ms(i) = m; sigma_ms(i) = sigma_m; epsks(i) = epsilon_over_k; mu_Cm(i) = get_dipole_Cm(); Q_Cm2(i) = get_quadrupole_Cm2(); + nmu(i) = (j.contains("nmu") ? j["nmu"].get<double>() : 0.0); + nQ(i) = (j.contains("nQ") ? j["nQ"].get<double>() : 0.0); + i++; + }; + + using namespace SAFTpolar; + if (polar_model == "GrossVrabec"){ + auto mustar2 = (mustar2factor*mu_Cm.pow(2)/(ms*epsks*sigma_ms.pow(3))).eval(); + auto Qstar2 = (Qstar2factor*Q_Cm2.pow(2)/(ms*epsks*sigma_ms.pow(5))).eval(); + auto polar = MultipolarContributionGrossVrabec(ms, sigma_ms*1e10, epsks, mustar2, nmu, Qstar2, nQ); + return SAFTVRMieMixture(SAFTVRMieMixture::build_chain(coeffs, kmat), std::move(polar)); + } + else if (polar_model == "GubbinsTwu+Luckas"){ + using MCGTL = MultipolarContributionGubbinsTwu<LuckasJIntegral, LuckasKIntegral>; + auto mubar2 = (mustar2factor*mu_Cm.pow(2)/(epsks*sigma_ms.pow(3))).eval(); + auto Qbar2 = (Qstar2factor*Q_Cm2.pow(2)/(epsks*sigma_ms.pow(5))).eval(); + auto polar = MCGTL(sigma_ms, epsks, mubar2, Qbar2); + return SAFTVRMieMixture(SAFTVRMieMixture::build_chain(coeffs, kmat), std::move(polar)); + } + else if (polar_model == "GubbinsTwu+GubbinsTwu"){ + using MCGG = MultipolarContributionGubbinsTwu<GubbinsTwuJIntegral, GubbinsTwuKIntegral>; + auto mubar2 = (mustar2factor*mu_Cm.pow(2)/(epsks*sigma_ms.pow(3))).eval(); + auto Qbar2 = (Qstar2factor*Q_Cm2.pow(2)/(epsks*sigma_ms.pow(5))).eval(); + auto polar = MCGG(sigma_ms, epsks, mubar2, Qbar2); + return SAFTVRMieMixture(SAFTVRMieMixture::build_chain(coeffs, kmat), std::move(polar)); + } + else{ + throw teqp::InvalidArgument("didn't understand this polar_model:"+polar_model); + } + } + } + else{ + throw std::invalid_argument("you must provide names or coeffs, but not both"); + } +} } /* namespace SAFTVRMie */ }; // namespace teqp diff --git a/src/tests/catch_test_SAFTpolar.cxx b/src/tests/catch_test_SAFTpolar.cxx index 6f84f68b3a8f3915629cd4272469e63c98898f6f..1a02bd81c3770330ccfa93097ddf4d1ed3499170 100644 --- a/src/tests/catch_test_SAFTpolar.cxx +++ b/src/tests/catch_test_SAFTpolar.cxx @@ -1,8 +1,10 @@ #include <catch2/catch_test_macros.hpp> #include <catch2/catch_approx.hpp> - using Catch::Approx; +#include "nlohmann/json.hpp" +#include "teqp/cpp/teqpcpp.hpp" + #include "teqp/models/saft/correlation_integrals.hpp" #include "teqp/models/saft/polar_terms.hpp" #include "teqp/types.hpp" @@ -25,6 +27,35 @@ TEST_CASE("Evaluation of K(xxx, yyy)", "[LuckasKnn]") CHECK(Kval45 == Approx(0.01541).margin(0.02)); } +TEST_CASE("Evaluation of J^{(n)}", "[checkJvals]") +{ + double Tstar = 2.0, rhostar = 0.4; + SECTION("Luckas v. G&T"){ + for (auto n = 4; n < 37; ++n){ + CAPTURE(n); + auto L = LuckasJIntegral(n).get_J(Tstar, rhostar); + auto G = GubbinsTwuJIntegral(n).get_J(Tstar, rhostar); + CHECK(L == Approx(G)); + } + } +} + +TEST_CASE("Evaluation of K^{(n,m)}", "[checkKvals]") +{ + double Tstar = 2.0, rhostar = 0.4; + SECTION("Luckas v. G&T"){ + std::vector<std::tuple<int, int>> nm = {{222,333},{233,344},{334,445},{444,555}}; + for (auto [n,m] : nm){ + auto L = LuckasKIntegral(n,m).get_K(Tstar, rhostar); + auto G = GubbinsTwuKIntegral(n,m).get_K(Tstar, rhostar); + CAPTURE(n); + CAPTURE(m); + CHECK(L == Approx(G)); + } + } +} + + using MCGTL = MultipolarContributionGubbinsTwu<LuckasJIntegral, LuckasKIntegral>; using MCGG = MultipolarContributionGubbinsTwu<GubbinsTwuJIntegral, GubbinsTwuKIntegral>; @@ -69,3 +100,78 @@ TEST_CASE("Check derivative of |x|", "[diffabs]") CHECK(safe_abs2(std::complex<double>(-3.1, h)).imag()/h == -1); } } + +TEST_CASE("Check critical points with polarity terms", "[SAFTVRMiepolar]"){ + double rhostar_guess_init = 0.27; + double Tstar_guess_init = 1.5; + double ek = 100; + double sigma_m = 3e-10; + auto j = nlohmann::json::parse(R"({ + "kind": "SAFT-VR-Mie", + "model": { + "coeffs": [ + { + "name": "Stockmayer126", + "m": 1.0, + "sigma_m": 3e-10, + "epsilon_over_k": 100, + "lambda_r": 12, + "lambda_a": 6, + "BibTeXKey": "me" + } + ] + } + })"); + CHECK_NOTHROW(teqp::cppinterface::make_model(j)); + auto nonpolar_model = teqp::cppinterface::make_model(j); + +// SECTION("Backwards-compat Gross&Vrabec"){ +// const bool print = false; +// double Tstar_guess = Tstar_guess_init, rhostar_guess = rhostar_guess_init; +// if (print) std::cout << "(mu^*)^2, T^*, rho^*" << std::endl; +// +// auto critpure = nonpolar_model->solve_pure_critical(Tstar_guess*ek, rhostar_guess/(N_A*pow(sigma_m, 3))); +// Tstar_guess = std::get<0>(critpure)/ek; +// rhostar_guess = std::get<1>(critpure)*N_A*pow(sigma_m, 3); +// if (print) std::cout << "0, " << Tstar_guess << ", " << rhostar_guess << std::endl; +// +// for (double mustar2 = 0.1; mustar2 < 5; mustar2 += 0.1){ +// j["model"]["coeffs"][0]["(mu^*)^2"] = mustar2; +// j["model"]["coeffs"][0]["nmu"] = 1; +// auto model = teqp::cppinterface::make_model(j); +// auto pure = model->solve_pure_critical(Tstar_guess*ek, rhostar_guess/(N_A*pow(sigma_m, 3))); +// if (print) std::cout << mustar2 << ", " << std::get<0>(pure)/ek << ", " << std::get<1>(pure)*N_A*pow(sigma_m, 3) << std::endl; +// Tstar_guess = std::get<0>(pure)/ek; +// rhostar_guess = std::get<1>(pure)*N_A*pow(sigma_m, 3); +// } +// CHECK(Tstar_guess == Approx(2.29743)); +// CHECK(rhostar_guess == Approx(0.221054)); +// } + + SECTION("With Gross&Vrabec"){ + for (std::string polar_model : {"GrossVrabec", "GubbinsTwu+Luckas", "GubbinsTwu+GubbinsTwu"}){ + const bool print = true; + double Tstar_guess = Tstar_guess_init, rhostar_guess = rhostar_guess_init; + if (print) std::cout << "(mu^*)^2, T^*, rho^*" << std::endl; + + auto critpure = nonpolar_model->solve_pure_critical(Tstar_guess*ek, rhostar_guess/(N_A*pow(sigma_m, 3))); + Tstar_guess = std::get<0>(critpure)/ek; + rhostar_guess = std::get<1>(critpure)*N_A*pow(sigma_m, 3); + if (print) std::cout << "0, " << Tstar_guess << ", " << rhostar_guess << std::endl; + + double mustar2factor = 1.0/(4*static_cast<double>(EIGEN_PI)*8.8541878128e-12*1.380649e-23); + j["model"]["polar_model"] = polar_model; + for (double mustar2 = 0.1; mustar2 < 5; mustar2 += 0.1){ + j["model"]["coeffs"][0]["mu_Cm"] = sqrt(mustar2/mustar2factor*(ek*pow(sigma_m, 3))); + j["model"]["coeffs"][0]["nmu"] = 1; + auto model = teqp::cppinterface::make_model(j); + auto pure = model->solve_pure_critical(Tstar_guess*ek, rhostar_guess/(N_A*pow(sigma_m, 3))); + if (print) std::cout << mustar2 << ", " << std::get<0>(pure)/ek << ", " << std::get<1>(pure)*N_A*pow(sigma_m, 3) << std::endl; + Tstar_guess = std::get<0>(pure)/ek; + rhostar_guess = std::get<1>(pure)*N_A*pow(sigma_m, 3); + } +// CHECK(Tstar_guess == Approx(2.29743)); +// CHECK(rhostar_guess == Approx(0.221054)); + } + } +}