/*** \brief This file contains the contributions that can be composed together to form SAFT models */ #pragma once #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" #include "teqp/models/saft/polar_terms.hpp" #include <optional> #include <variant> namespace teqp { namespace SAFTVRMie { /// Coefficients for one fluid struct SAFTVRMieCoeffs { std::string name; ///< Name of fluid double m = -1, ///< number of segments sigma_m = -1, ///< [m] segment diameter epsilon_over_k = -1, ///< [K] depth of pair potential divided by Boltzman constant lambda_a = -1, ///< The attractive exponent (the 6 in LJ 12-6 potential) lambda_r = -1, ///< The repulsive exponent (the 12 in LJ 12-6 potential) mustar2 = 0, ///< nondimensional, the reduced dipole moment squared nmu = 0, ///< number of dipolar segments Qstar2 = 0, ///< nondimensional, the reduced quadrupole squared nQ = 0; ///< number of quadrupolar segments std::string BibTeXKey; ///< The BibTeXKey for the reference for these coefficients }; /// Manager class for SAFT-VR-Mie coefficients class SAFTVRMieLibrary { std::map<std::string, SAFTVRMieCoeffs> coeffs; public: SAFTVRMieLibrary() { insert_normal_fluid("Methane", 1.0000, 3.7412e-10, 153.36, 12.650, 6, "Lafitte-JCP-2001"); insert_normal_fluid("Ethane", 1.4373, 3.7257e-10, 206.12, 12.400, 6, "Lafitte-JCP-2001"); insert_normal_fluid("Propane", 1.6845, 3.9056e-10, 239.89, 13.006, 6, "Lafitte-JCP-2001"); } void insert_normal_fluid(const std::string& name, double m, const double sigma_m, const double epsilon_over_k, const double lambda_r, const double lambda_a, const std::string& BibTeXKey) { SAFTVRMieCoeffs coeff; coeff.name = name; coeff.m = m; coeff.sigma_m = sigma_m; coeff.epsilon_over_k = epsilon_over_k; coeff.lambda_r = lambda_r; coeff.lambda_a = lambda_a; coeff.BibTeXKey = BibTeXKey; coeffs.insert(std::pair<std::string, SAFTVRMieCoeffs>(name, coeff)); } const auto& get_normal_fluid(const std::string& name) { auto it = coeffs.find(name); if (it != coeffs.end()) { return it->second; } else { throw std::invalid_argument("Bad name:" + name); } } auto get_coeffs(const std::vector<std::string>& names){ std::vector<SAFTVRMieCoeffs> c; for (auto n : names){ c.push_back(get_normal_fluid(n)); } return c; } }; /// Things that only depend on the components themselves, but not on composition, temperature, or density struct SAFTVRMieChainContributionTerms{ private: /// The matrix of coefficients needed to evaluate f_k const Eigen::Matrix<double, 7, 7> phi{(Eigen::Matrix<double, 7, 7>() << 7.5365557, -359.44, 1550.9, -1.19932, -1911.28, 9236.9, 10, -37.60463, 1825.6, -5070.1, 9.063632, 21390.175, -129430, 10, 71.745953, -3168.0, 6534.6, -17.9482, -51320.7, 357230, 0.57, -46.83552, 1884.2, -3288.7, 11.34027, 37064.54, -315530, -6.7, -2.467982, -0.82376, -2.7171, 20.52142, 1103.742, 1390.2, -8, -0.50272, -3.1935, 2.0883, -56.6377, -3264.61, -4518.2, 0, 8.0956883, 3.7090, 0, 40.53683, 2556.181, 4241.6, 0 ).finished()}; /// The matrix used to obtain the parameters c_1, c_2, c_3, and c_4 in Eq. A18 const Eigen::Matrix<double, 4, 4> A{(Eigen::Matrix<double, 4, 4>() << 0.81096, 1.7888, -37.578, 92.284, 1.0205, -19.341, 151.26, -463.50, -1.9057, 22.845, -228.14, 973.92, 1.0885, -6.1962, 106.98, -677.64).finished()}; // Eq. A48 auto get_lambda_k_ij(const Eigen::ArrayXd& lambda_k) const{ Eigen::ArrayXXd mat(N,N); for (auto i = 0; i < lambda_k.size(); ++i){ for (auto j = i; j < lambda_k.size(); ++j){ mat(i,j) = 3 + sqrt((lambda_k(i)-3)*(lambda_k(j)-3)); mat(j,i) = mat(i,j); } } return mat; } /// Eq. A3 auto get_C_ij() const{ Eigen::ArrayXXd C(N,N); for (auto i = 0; i < N; ++i){ for (auto j = i; j < N; ++j){ C(i,j) = lambda_r_ij(i,j)/(lambda_r_ij(i,j)-lambda_a_ij(i,j))*pow(lambda_r_ij(i,j)/lambda_a_ij(i,j), lambda_a_ij(i,j)/(lambda_r_ij(i,j)-lambda_a_ij(i,j))); C(j,i) = C(i,j); // symmetric } } return C; } // Eq. A26 auto get_fkij() const{ std::vector<Eigen::ArrayXXd> f_(8); // 0-th element is present, but not initialized for (auto k = 1; k < 8; ++k){ f_[k].resize(N,N); }; for (auto k = 1; k < 8; ++k){ auto phik = phi.col(k-1); // phi matrix is indexed to start at 1, but our matrix starts at 0 Eigen::ArrayXXd num(N,N), den(N,N); num.setZero(), den.setZero(); for (auto n = 0; n < 4; ++n){ num += phik[n]*pow(alpha_ij, n); } for (auto n = 4; n < 7; ++n){ den += phik[n]*pow(alpha_ij, n-3); } f_[k] = num/(1 + den); } return f_; } /// Eq. A45 auto get_sigma_ij() const{ Eigen::ArrayXXd sigma(N,N); for (auto i = 0; i < N; ++i){ for (auto j = i; j < N; ++j){ sigma(i,j) = (sigma_A(i) + sigma_A(j))/2.0; sigma(j,i) = sigma(i,j); // symmetric } } return sigma; } /// Eq. A55 auto get_epsilon_ij() const{ Eigen::ArrayXXd eps_(N,N); for (auto i = 0; i < N; ++i){ for (auto j = i; j < N; ++j){ eps_(i,j) = (1.0-kmat(i,j))*sqrt(pow(sigma_ij(i,i),3)*pow(sigma_ij(j,j),3)*epsilon_over_k(i)*epsilon_over_k(j))/pow(sigma_ij(i,j), 3); eps_(j,i) = eps_(i,j); // symmetric } } return eps_; } std::size_t get_N(){ auto sizes = (Eigen::ArrayXd(5) << m.size(), epsilon_over_k.size(), sigma_A.size(), lambda_a.size(), lambda_r.size()).finished(); if (sizes.mean() != sizes.minCoeff()){ throw teqp::InvalidArgument("sizes of pure component arrays are not all the same"); } return sizes[0]; } /// Eq. A18 for the attractive exponents auto get_cij(const Eigen::ArrayXXd& lambdaij) const{ std::vector<Eigen::ArrayXXd> cij(4); for (auto n = 0; n < 4; ++n){ cij[n].resize(N,N); }; for (auto i = 0; i < N; ++i){ for (auto j = i; j < N; ++j){ using CV = Eigen::Vector<double, 4>; const CV b{(CV() << 1, 1.0/lambdaij(i,j), 1.0/pow(lambdaij(i,j),2), 1.0/pow(lambdaij(i,j),3)).finished()}; auto c1234 = (A*b).eval(); cij[0](i,j) = c1234(0); cij[1](i,j) = c1234(1); cij[2](i,j) = c1234(2); cij[3](i,j) = c1234(3); } } return cij; } /// Eq. A18 for the attractive exponents auto get_canij() const{ return get_cij(lambda_a_ij); } /// Eq. A18 for 2x the attractive exponents auto get_c2anij() const{ return get_cij(2.0*lambda_a_ij); } /// Eq. A18 for the repulsive exponents auto get_crnij() const{ return get_cij(lambda_r_ij); } /// Eq. A18 for the 2x the repulsive exponents auto get_c2rnij() const{ return get_cij(2.0*lambda_r_ij); } /// Eq. A18 for the 2x the repulsive exponents auto get_carnij() const{ return get_cij(lambda_r_ij + lambda_a_ij); } public: // One entry per component const Eigen::ArrayXd m, epsilon_over_k, sigma_A, lambda_a, lambda_r; const Eigen::ArrayXXd kmat; const std::size_t N; // Calculated matrices for the ij pair const Eigen::ArrayXXd lambda_r_ij, lambda_a_ij, C_ij, alpha_ij, sigma_ij, epsilon_ij; // Matrices of parameters const std::vector<Eigen::ArrayXXd> crnij, canij, c2rnij, c2anij, carnij; const std::vector<Eigen::ArrayXXd> fkij; // Matrices of parameters SAFTVRMieChainContributionTerms( const Eigen::ArrayXd& m, const Eigen::ArrayXd& epsilon_over_k, const Eigen::ArrayXd& sigma_m, const Eigen::ArrayXd& lambda_r, const Eigen::ArrayXd& lambda_a, const Eigen::ArrayXXd& kmat) : m(m), epsilon_over_k(epsilon_over_k), sigma_A(sigma_m*1e10), lambda_a(lambda_a), lambda_r(lambda_r), kmat(kmat), N(get_N()), lambda_r_ij(get_lambda_k_ij(lambda_r)), lambda_a_ij(get_lambda_k_ij(lambda_a)), C_ij(get_C_ij()), alpha_ij(C_ij*(1/(lambda_a_ij-3) - 1/(lambda_r_ij-3))), sigma_ij(get_sigma_ij()), epsilon_ij(get_epsilon_ij()), crnij(get_crnij()), canij(get_canij()), c2rnij(get_c2rnij()), c2anij(get_c2anij()), carnij(get_carnij()), fkij(get_fkij()) {} /// Eq. A2 from Lafitte template<typename RType> auto get_uii_over_kB(std::size_t i, const RType& r) const { auto rstarinv = forceeval(sigma_A[i]/r); return forceeval(C_ij(i,i)*epsilon_over_k[i]*(pow(rstarinv, lambda_r[i]) - pow(rstarinv, lambda_a[i]))); } /// Solve for the value of \f$j=\sigma/r\f$ for which the integrand in \f$d_{ii}\f$ becomes equal to 1 to numerical precision template <typename TType> auto get_j_cutoff_dii(std::size_t i, const TType &T) const { auto lambda_a_ = lambda_a(i), lambda_r_ = lambda_r(i); auto EPS = std::numeric_limits<decltype(getbaseval(T))>::epsilon(); auto K = forceeval(log(-log(EPS)*T/(C_ij(i,i)*epsilon_ij(i,i)))); auto j0 = forceeval(exp(K/lambda_r_)); // this was proposed by longemen3000 (Andrés Riedemann) auto kappa = C_ij(i,i)*epsilon_ij(i,i); // Function to return residual and its derivatives w.r.t. auto fgh = [&kappa, &lambda_r_, &lambda_a_, &T, &EPS](auto j){ auto jlr = pow(j, lambda_r_), jla = pow(j, lambda_a_); auto u = kappa*(jlr - jla); auto uprime = kappa*(lambda_r_*jlr - lambda_a_*jla)/j; auto uprime2 = kappa*(lambda_r_*(lambda_r_-1.0)*jlr - lambda_a_*(lambda_a_-1.0)*jla)/(j*j); return std::make_tuple(forceeval(-u/T-log(EPS)), forceeval(-uprime/T), forceeval(-uprime2/T)); }; TType j = j0; for (auto counter = 0; counter <= 3; ++counter){ // Halley's method steps auto [R, Rprime, Rprime2] = fgh(j); auto denominator = 2.0*Rprime*Rprime-R*Rprime2; if (getbaseval(denominator) < EPS){ break; } j -= 2.0*R*Rprime/denominator; } double jbase = getbaseval(j); if (jbase < 1.0){ throw teqp::IterationFailure("Cannot obtain a value of j"); } return j; } /** \note Eq. A9 from Lafitte The calculation of the diameter is based upon \f[ d_{ii} = \int_0^{\sigma_{ii}}(1-\exp(-\beta u_{ii}^{\rm Mie}(r)){\rm d}r \f] which is broken up into two parts: \f[ d = \int_0^{r_{\rm cut}} 1 {\rm d} r + \int_{r_{\rm cut}}^{\sigma_{ii}} [1-\exp(-\beta u_{ii}^{\rm Mie}(r))] {\rm d}r \f] but the integrand is basically constant (to numerical precision) from 0 to some cutoff value of \f$r\f$, which we'll call \f$r_{\rm cut}\f$. So first we need to find the value of \f$r_{\rm cut}\f$ that makes the integrand take its constant value, which is explained well in the paper from Aasen (https://github.com/ClapeyronThermo/Clapeyron.jl/issues/152#issuecomment-1480324192). Finding the cutoff value is obtained when \f[ \exp(-\beta u_{ii}^{\rm Mie}(r)) = EPS \f] where EPS is the numerical precision of the floating point type. Taking the logs of both sides, \f[ -\beta u_{ii}^{\rm Mie} = \ln(EPS) \f] To get a starting value, it is first assumed that only the repulsive contribution contributes to the potential, yielding \f$u^{\rm rep} = C\epsilon(\sigma/r)^{\lambda_r}\f$ (with \f$C\f$ the same as the full potential with attraction) which yields \f[ -\beta C\epsilon(\sigma/r)^{\lambda_r} = \ln(EPS) \f] and \f[ (\sigma/r)_{\rm guess} = (-\ln(EPS)/(\beta C \epsilon))^{1/\lambda_r} \f] Then we solve for the residual \f$R(r)=0\f$, where \f$R_0=\exp(-u/T)-EPS\f$. Equivalently we can write the residual in logarithmic terms as \f$R=-u/T-\ln(EPS)\f$. This simplifies the rootfinding as you need \f$R\f$, \f$R'\f$ and \f$R''\f$ to apply Halley's method, which are themselves quite straightforward to obtain because \f$R'=-u'/T\f$, \f$R''=-u''/T\f$, where the primes are derivatives taken with respect to \f$\sigma/r\f$. */ template <typename TType> TType get_dii(std::size_t i, const TType &T) const{ std::function<TType(TType)> integrand = [this, i, &T](const TType& r){ return forceeval(1.0-exp(-this->get_uii_over_kB(i, r)/T)); }; // Sum of the two integrals, one is constant, the other is from integration auto rcut = forceeval(sigma_A[i]/get_j_cutoff_dii(i, T)); auto integral_contribution = quad<10, TType, TType>(integrand, rcut, sigma_A[i]); auto d = forceeval(rcut + integral_contribution); if (getbaseval(d) > sigma_A[i]){ throw teqp::IterationFailure("Value of d is larger than sigma; this is impossible"); } return d; } template <typename TType> auto get_dmat(const TType &T) const{ Eigen::Array<TType, Eigen::Dynamic, Eigen::Dynamic> d(N,N); // For the pure components, by integration for (auto i = 0; i < N; ++i){ d(i,i) = get_dii(i, T); } // The cross terms, using the linear mixing rule for (auto i = 0; i < N; ++i){ for (auto j = i+1; j < N; ++j){ d(i,j) = (d(i,i) + d(j,j))/2.0; d(j,i) = d(i,j); } } return d; } // Calculate core parameters that depend on temperature, volume, and composition template <typename TType, typename RhoType, typename VecType> auto get_core_calcs(const TType& T, const RhoType& rhomolar, const VecType& molefracs) const{ if (molefracs.size() != N){ throw teqp::InvalidArgument("Length of molefracs of "+std::to_string(molefracs.size()) + " does not match the model size of"+std::to_string(N)); } using FracType = std::decay_t<decltype(molefracs[0])>; using NumType = std::common_type_t<TType, RhoType, FracType>; // Things that are easy to calculate // .... auto dmat = get_dmat(T); // Matrix of diameters of pure and cross terms auto rhoN = forceeval(rhomolar*N_A); // Number density, in molecules/m^3 auto mbar = forceeval((molefracs*m).sum()); // Mean number of segments, dimensionless auto rhos = forceeval(rhoN*mbar/1e30); // Mean segment number density, in segments/A^3 auto xs = forceeval((m*molefracs/mbar).eval()); // Segment fractions constexpr double MY_PI = static_cast<double>(EIGEN_PI); auto pi6 = MY_PI/6; using TRHOType = std::common_type_t<std::decay_t<TType>, std::decay_t<RhoType>, std::decay_t<decltype(molefracs[0])>, std::decay_t<decltype(m[0])>>; Eigen::Array<TRHOType, 4, 1> zeta; for (auto l = 0; l < 4; ++l){ TRHOType summer = 0.0; for (auto i = 0; i < N; ++i){ summer += xs(i)*powi(dmat(i,i), l); } zeta(l) = forceeval(pi6*rhos*summer); } NumType summer_zeta_x = 0.0; TRHOType summer_zeta_x_bar = 0.0; for (auto i = 0; i < N; ++i){ for (auto j = 0; j < N; ++j){ summer_zeta_x += xs(i)*xs(j)*powi(dmat(i,j), 3)*rhos; summer_zeta_x_bar += xs(i)*xs(j)*powi(sigma_ij(i,j), 3); } } auto zeta_x = forceeval(pi6*summer_zeta_x); // Eq. A13 auto zeta_x_bar = forceeval(pi6*rhos*summer_zeta_x_bar); // Eq. A23 auto zeta_x_bar5 = forceeval(POW2(zeta_x_bar)*POW3(zeta_x_bar)); // (zeta_x_bar)^5 auto zeta_x_bar8 = forceeval(zeta_x_bar5*POW3(zeta_x_bar)); // (zeta_x_bar)^8 // Coefficients in the gdHSij term, do not depend on component, // so calculate them here auto X = forceeval(POW3(1.0 - zeta_x)), X3 = X; auto X2 = forceeval(POW2(1.0 - zeta_x)); auto k0 = forceeval(-log(1.0-zeta_x) + (42.0*zeta_x - 39.0*POW2(zeta_x) + 9.0*POW3(zeta_x) - 2.0*POW4(zeta_x))/(6.0*X3)); // Eq. A30 auto k1 = forceeval((POW4(zeta_x) + 6.0*POW2(zeta_x) - 12.0*zeta_x)/(2.0*X3)); auto k2 = forceeval(-3.0*POW2(zeta_x)/(8.0*X2)); auto k3 = forceeval((-POW4(zeta_x) + 3.0*POW2(zeta_x) + 3.0*zeta_x)/(6.0*X3)); // Pre-calculate the cubes of the diameters auto dmat3 = dmat.array().pow(3).eval(); NumType a1kB = 0.0; NumType a2kB2 = 0.0; NumType a3kB3 = 0.0; NumType alphar_chain = 0.0; NumType K_HS = get_KHS(zeta_x); NumType rho_dK_HS_drho = get_rhos_dK_HS_drhos(zeta_x); for (auto i = 0; i < N; ++i){ for (auto j = i; j < N; ++j){ NumType x_0_ij = sigma_ij(i,j)/dmat(i, j); // ----------------------- // Calculations for a_1/kB // ----------------------- auto I = [&x_0_ij](double lambda_ij){ return forceeval(-(pow(x_0_ij, 3-lambda_ij)-1.0)/(lambda_ij-3.0)); // Eq. A14 }; auto J = [&x_0_ij](double lambda_ij){ return forceeval(-(pow(x_0_ij, 4-lambda_ij)*(lambda_ij-3.0)-pow(x_0_ij, 3.0-lambda_ij)*(lambda_ij-4.0)-1.0)/((lambda_ij-3.0)*(lambda_ij-4.0))); // Eq. A15 }; auto Bhatij_a = this->get_Bhatij(zeta_x, X, I(lambda_a_ij(i,j)), J(lambda_a_ij(i,j))); auto Bhatij_2a = this->get_Bhatij(zeta_x, X, I(2*lambda_a_ij(i,j)), J(2*lambda_a_ij(i,j))); auto Bhatij_r = this->get_Bhatij(zeta_x, X, I(lambda_r_ij(i,j)), J(lambda_r_ij(i,j))); auto Bhatij_2r = this->get_Bhatij(zeta_x, X, I(2*lambda_r_ij(i,j)), J(2*lambda_r_ij(i,j))); auto Bhatij_ar = this->get_Bhatij(zeta_x, X, I(lambda_a_ij(i,j)+lambda_r_ij(i,j)), J(lambda_a_ij(i,j)+lambda_r_ij(i,j))); auto one_term = [this, &x_0_ij, &I, &J, &zeta_x, &X](double lambda_ij, const NumType& zeta_x_eff){ return forceeval( pow(x_0_ij, lambda_ij)*( this->get_Bhatij(zeta_x, X, I(lambda_ij), J(lambda_ij)) + this->get_a1Shatij(zeta_x_eff, lambda_ij) ) ); }; NumType zeta_x_eff_r = crnij[0](i,j)*zeta_x + crnij[1](i,j)*POW2(zeta_x) + crnij[2](i,j)*POW3(zeta_x) + crnij[3](i,j)*POW4(zeta_x); NumType zeta_x_eff_a = canij[0](i,j)*zeta_x + canij[1](i,j)*POW2(zeta_x) + canij[2](i,j)*POW3(zeta_x) + canij[3](i,j)*POW4(zeta_x); NumType dzeta_x_eff_dzetax_r = crnij[0](i,j) + crnij[1](i,j)*2*zeta_x + crnij[2](i,j)*3*POW2(zeta_x) + crnij[3](i,j)*4*POW3(zeta_x); NumType dzeta_x_eff_dzetax_a = canij[0](i,j) + canij[1](i,j)*2*zeta_x + canij[2](i,j)*3*POW2(zeta_x) + canij[3](i,j)*4*POW3(zeta_x); NumType a1ij = 2.0*MY_PI*rhos*dmat3(i,j)*epsilon_ij(i,j)*C_ij(i,j)*( one_term(lambda_a_ij(i,j), zeta_x_eff_a) - one_term(lambda_r_ij(i,j), zeta_x_eff_r) ); // divided by k_B NumType contribution = xs(i)*xs(j)*a1ij; double factor = (i == j) ? 1.0 : 2.0; // Off-diagonal terms contribute twice a1kB += contribution*factor; // -------------------------- // Calculations for a_2/k_B^2 // -------------------------- NumType zeta_x_eff_2r = c2rnij[0](i,j)*zeta_x + c2rnij[1](i,j)*POW2(zeta_x) + c2rnij[2](i,j)*POW3(zeta_x) + c2rnij[3](i,j)*POW4(zeta_x); NumType zeta_x_eff_2a = c2anij[0](i,j)*zeta_x + c2anij[1](i,j)*POW2(zeta_x) + c2anij[2](i,j)*POW3(zeta_x) + c2anij[3](i,j)*POW4(zeta_x); NumType zeta_x_eff_ar = carnij[0](i,j)*zeta_x + carnij[1](i,j)*POW2(zeta_x) + carnij[2](i,j)*POW3(zeta_x) + carnij[3](i,j)*POW4(zeta_x); NumType dzeta_x_eff_dzetax_2r = c2rnij[0](i,j) + c2rnij[1](i,j)*2*zeta_x + c2rnij[2](i,j)*3*POW2(zeta_x) + c2rnij[3](i,j)*4*POW3(zeta_x); NumType dzeta_x_eff_dzetax_ar = carnij[0](i,j) + carnij[1](i,j)*2*zeta_x + carnij[2](i,j)*3*POW2(zeta_x) + carnij[3](i,j)*4*POW3(zeta_x); NumType dzeta_x_eff_dzetax_2a = c2anij[0](i,j) + c2anij[1](i,j)*2*zeta_x + c2anij[2](i,j)*3*POW2(zeta_x) + c2anij[3](i,j)*4*POW3(zeta_x); NumType chi_ij = fkij[1](i,j)*zeta_x_bar + fkij[2](i,j)*zeta_x_bar5 + fkij[3](i,j)*zeta_x_bar8; auto a2ij = 0.5*K_HS*(1.0+chi_ij)*epsilon_ij(i,j)*POW2(C_ij(i,j))*(2*MY_PI*rhos*dmat3(i,j)*epsilon_ij(i,j))*( one_term(2.0*lambda_a_ij(i,j), zeta_x_eff_2a) -2.0*one_term(lambda_a_ij(i,j)+lambda_r_ij(i,j), zeta_x_eff_ar) +one_term(2.0*lambda_r_ij(i,j), zeta_x_eff_2r) ); // divided by k_B^2 NumType contributiona2 = xs(i)*xs(j)*a2ij; // Eq. A19 a2kB2 += contributiona2*factor; // -------------------------- // Calculations for a_3/k_B^3 // -------------------------- auto a3ij = -POW3(epsilon_ij(i,j))*fkij[4](i,j)*zeta_x_bar*exp( fkij[5](i,j)*zeta_x_bar + fkij[6](i,j)*POW2(zeta_x_bar) ); // divided by k_B^3 NumType contributiona3 = xs(i)*xs(j)*a3ij; // Eq. A25 a3kB3 += contributiona3*factor; if (i == j){ // ------------------ // Chain contribution // ------------------ // Eq. A29 auto gdHSii = exp(k0 + k1*x_0_ij + k2*POW2(x_0_ij) + k3*POW3(x_0_ij)); // The g1 terms // .... // This is the function for the second part (not the partial) that goes in g_{1,ii}, // divided by 2*PI*d_ij^3*epsilon*rhos auto g1_term = [&one_term](double lambda_ij, const NumType& zeta_x_eff){ return forceeval(lambda_ij*one_term(lambda_ij, zeta_x_eff)); }; auto g1_noderivterm = -C_ij(i,i)*(g1_term(lambda_a_ij(i,i), zeta_x_eff_a)-g1_term(lambda_r_ij(i,i), zeta_x_eff_r)); // Bhat = B*rho*kappa; diff(Bhat, rho) = Bhat + rho*dBhat/drho; kappa = 2*pi*eps*d^3 // This is the function for the partial derivative rhos*(da1ij/drhos), // divided by 2*PI*d_ij^3*epsilon*rhos auto rhosda1iidrhos_term = [this, &x_0_ij, &I, &J, &zeta_x, &X](double lambda_ij, const NumType& zeta_x_eff, const NumType& dzetaxeff_dzetax, const NumType& Bhatij){ auto I_ = I(lambda_ij); auto J_ = J(lambda_ij); auto rhosda1Sdrhos = this->get_rhoda1Shatijdrho(zeta_x, zeta_x_eff, dzetaxeff_dzetax, lambda_ij); auto rhosdBdrhos = this->get_rhodBijdrho(zeta_x, X, I_, J_, Bhatij); return forceeval(pow(x_0_ij, lambda_ij)*(rhosda1Sdrhos + rhosdBdrhos)); }; // This is rhos*d(a_1ij)/drhos/(2*pi*d^3*eps*rhos) auto da1iidrhos_term = C_ij(i,j)*( rhosda1iidrhos_term(lambda_a_ij(i,i), zeta_x_eff_a, dzeta_x_eff_dzetax_a, Bhatij_a) -rhosda1iidrhos_term(lambda_r_ij(i,i), zeta_x_eff_r, dzeta_x_eff_dzetax_r, Bhatij_r) ); auto g1ii = 3.0*da1iidrhos_term + g1_noderivterm; // The g2 terms // .... // This is the second part (not the partial deriv.) that goes in g_{2,ii}, // divided by 2*PI*d_ij^3*epsilon*rhos auto g2_noderivterm = -POW2(C_ij(i,i))*K_HS*( lambda_a_ij(i,j)*one_term(2*lambda_a_ij(i,j), zeta_x_eff_2a) -(lambda_a_ij(i,j)+lambda_r_ij(i,j))*one_term(lambda_a_ij(i,j)+lambda_r_ij(i,j), zeta_x_eff_ar) +lambda_r_ij(i,j)*one_term(2*lambda_r_ij(i,j), zeta_x_eff_2r) ); // This is [rhos*d(a_2ij/(1+chi_ij))/drhos]/(2*pi*d^3*eps*rhos) auto da2iidrhos_term = 0.5*POW2(C_ij(i,j))*( rho_dK_HS_drho*( one_term(2.0*lambda_a_ij(i,j), zeta_x_eff_2a) -2.0*one_term(lambda_a_ij(i,j)+lambda_r_ij(i,j), zeta_x_eff_ar) +one_term(2.0*lambda_r_ij(i,j), zeta_x_eff_2r)) +K_HS*( rhosda1iidrhos_term(2.0*lambda_a_ij(i,i), zeta_x_eff_2a, dzeta_x_eff_dzetax_2a, Bhatij_2a) -2.0*rhosda1iidrhos_term(lambda_a_ij(i,i)+lambda_r_ij(i,i), zeta_x_eff_ar, dzeta_x_eff_dzetax_ar, Bhatij_ar) +rhosda1iidrhos_term(2.0*lambda_r_ij(i,i), zeta_x_eff_2r, dzeta_x_eff_dzetax_2r, Bhatij_2r) ) ); auto g2MCAij = 3.0*da2iidrhos_term + g2_noderivterm; auto betaepsilon = epsilon_ij(i,i)/T; // (1/(kB*T))/epsilon auto theta = exp(betaepsilon)-1.0; auto phi7 = phi.col(6); auto gamma_cij = phi7(0)*(-tanh(phi7(1)*(phi7(2)-alpha_ij(i,j)))+1.0)*zeta_x_bar*theta*exp(phi7(3)*zeta_x_bar + phi7(4)*POW2(zeta_x_bar)); // Eq. A37 auto g2ii = (1.0+gamma_cij)*g2MCAij; NumType giiMie = gdHSii*exp((betaepsilon*g1ii + POW2(betaepsilon)*g2ii)/gdHSii); alphar_chain -= molefracs[i]*(m[i]-1.0)*log(giiMie); } } } auto ahs = get_a_HS(rhos, zeta); // Eq. A5 from Lafitte, multiplied by mbar auto alphar_mono = forceeval(mbar*(ahs + a1kB/T + a2kB2/(T*T) + a3kB3/(T*T*T))); using dmat_t = decltype(dmat); using rhos_t = decltype(rhos); using rhoN_t = decltype(rhoN); using mbar_t = decltype(mbar); using xs_t = decltype(xs); using zeta_t = decltype(zeta); using zeta_x_t = decltype(zeta_x); using zeta_x_bar_t = decltype(zeta_x_bar); using alphar_mono_t = decltype(alphar_mono); using a1kB_t = decltype(a1kB); using a2kB2_t = decltype(a2kB2); using a3kB3_t = decltype(a3kB3); using alphar_chain_t = decltype(alphar_chain); struct vals{ dmat_t dmat; rhos_t rhos; rhoN_t rhoN; mbar_t mbar; xs_t xs; zeta_t zeta; zeta_x_t zeta_x; zeta_x_bar_t zeta_x_bar; alphar_mono_t alphar_mono; a1kB_t a1kB; a2kB2_t a2kB2; a3kB3_t a3kB3; alphar_chain_t alphar_chain; }; return vals{dmat, rhos, rhoN, mbar, xs, zeta, zeta_x, zeta_x_bar, alphar_mono, a1kB, a2kB2, a3kB3, alphar_chain}; } /// Eq. A21 from Lafitte template<typename RhoType> auto get_KHS(const RhoType& pf) const { return forceeval(pow(1.0-pf,4)/(1.0 + 4.0*pf + 4.0*pf*pf - 4.0*pf*pf*pf + pf*pf*pf*pf)); } /** \f[ \rho_s\frac{\partial K_{HS}}{\partial \rho_s} = \zeta\frac{\partial K_{HS}}{\partial \zeta} \f] */ template<typename RhoType> auto get_rhos_dK_HS_drhos(const RhoType& zeta_x) const { auto num = -4.0*POW3(zeta_x - 1.0)*(POW2(zeta_x) - 5.0*zeta_x - 2.0); auto den = POW2(POW4(zeta_x) - 4.0*POW3(zeta_x) + 4.0*POW2(zeta_x) + 4.0*zeta_x + 1.0); return forceeval(num/den*zeta_x); } /// Eq. A6 from Lafitte, accounting for the case of rho_s=0, for which the limit is zero template<typename RhoType, typename ZetaType> auto get_a_HS(const RhoType& rhos, const Eigen::Array<ZetaType, 4, 1>& zeta) const{ constexpr double MY_PI = static_cast<double>(EIGEN_PI); if (getbaseval(rhos) == 0){ // The way in which the function goes to zero is subtle, and the factor of 4 accounts for the contributions from each term return forceeval(4.0*zeta[3]); } else{ return forceeval(6.0/(MY_PI*rhos)*(3.0*zeta[1]*zeta[2]/(1.0-zeta[3]) + POW3(zeta[2])/(zeta[3]*POW2(1.0-zeta[3])) + (POW3(zeta[2])/POW2(zeta[3])-zeta[0])*log(1.0-zeta[3]))); } } /** \note Starting from Eq. A12 from Lafitte Defining: \f[ \hat B_{ij} \equiv \frac{B_{ij}}{2\pi\epsilon_{ij}d^3_{ij}\rho_s} = \frac{1-\zeta_x/2}{(1-\zeta_x)^3}I-\frac{9\zeta_x(1+\zeta_x)}{2(1-\zeta_x)^3}J \f] */ template<typename ZetaType, typename IJ> auto get_Bhatij(const ZetaType& zeta_x, const ZetaType& one_minus_zeta_x3, const IJ& I, const IJ& J) const{ return forceeval( (1.0-zeta_x/2.0)/one_minus_zeta_x3*I - 9.0*zeta_x*(1.0+zeta_x)/(2.0*one_minus_zeta_x3)*J ); } /** \f[ B = \hat B_{ij}\kappa \rho_s \f] \f[ \left(\frac{\partial B_{ij}}{\partial \rho_s}\right)_{T,\vec{z}} = \kappa\left(\hat B + \zeta_x \frac{\partial \hat B}{\partial \zeta_x}\right) \f] and thus \f[ \rho_s \left(\frac{\partial B_{ij}}{\partial \rho_s}\right)_{T,\vec{z}} = \hat B + \zeta_x \frac{\partial \hat B}{\partial \zeta_x} \f] */ template<typename ZetaType, typename IJ> auto get_rhodBijdrho(const ZetaType& zeta_x, const ZetaType& one_minus_zeta_x3, const IJ& I, const IJ& J, const ZetaType& Bhatij) const{ auto dBhatdzetax = (-3.0*I*(zeta_x - 2.0) - 27.0*J*zeta_x*(zeta_x + 1.0) + (zeta_x - 1.0)*(I + 9.0*J*zeta_x + 9.0*J*(zeta_x + 1.0)))/(2.0*POW4(1.0-zeta_x)); return forceeval(Bhatij + dBhatdzetax*zeta_x); } /** \note Starting from Eq. A16 from Lafitte \f[ \hat a^S_{1,ii} = \frac{a^S_{1,ii}}{2\pi\epsilon_{ij}d^3_{ij}\rho_s} \f] so \f[ a^S_{1,ii} = \kappa\rho_s\hat a^S_{1,ii} \f] */ template<typename ZetaType> auto get_a1Shatij(const ZetaType& zeta_x_eff, double lambda_ij) const{ return forceeval( -1.0/(lambda_ij-3.0)*(1.0-zeta_x_eff/2.0)/POW3(forceeval(1.0-zeta_x_eff)) ); } /** \f[ \left(\frac{\partial a^S_{1,ii}}{\partial \rho_s}\right)_{T,\vec{z}} = \kappa\left(\hat a^S_{1,ii} + \rho_s\frac{\partial \hat a^S_{1,ii}}{\partial \rho_s} \right) \f] \f[ \left(\frac{\partial a^S_{1,ii}}{\partial \rho_s}\right)_{T,\vec{z}} = \kappa\left(\hat a^S_{1,ii} + \rho_s\frac{\partial \hat a^S_{1,ii}}{\partial \zeta_{x,eff}}\frac{\partial \zeta_{x,eff}}{\partial \zeta_x}\frac{\partial \zeta_x}{\partial \rho_s} \right) \f] since \f$\rho_s\frac{\partial \zeta_x}{\partial \rho_s} = \zeta_x\f$ */ template<typename ZetaType> auto get_rhoda1Shatijdrho(const ZetaType& zeta_x, const ZetaType& zeta_x_eff, const ZetaType& dzetaxeffdzetax, double lambda_ij) const{ auto zetaxda1Shatdzetax = ((2.0*zeta_x_eff - 5.0)*dzetaxeffdzetax)/(2.0*(lambda_ij-3)*POW4(zeta_x_eff-1.0))*zeta_x; return forceeval(get_a1Shatij(zeta_x_eff, lambda_ij) + zetaxda1Shatdzetax); } }; /** \brief A class used to evaluate mixtures using the SAFT-VR-Mie model */ class SAFTVRMieMixture { private: std::vector<std::string> names; const SAFTVRMieChainContributionTerms terms; const std::optional<SAFTpolar::multipolar_contributions_variant> polar; // Can be present or not static void check_kmat(const Eigen::ArrayXXd& kmat, std::size_t N) { if (kmat.size() == 0){ return; } if (kmat.cols() != kmat.rows()) { throw teqp::InvalidArgument("kmat rows and columns are not identical"); } if (kmat.cols() != N) { throw teqp::InvalidArgument("kmat needs to be a square matrix the same size as the number of components"); } }; static auto get_coeffs_from_names(const std::vector<std::string> &names){ SAFTVRMieLibrary library; return library.get_coeffs(names); } public: static auto build_chain(const std::vector<SAFTVRMieCoeffs> &coeffs, const std::optional<Eigen::ArrayXXd>& kmat){ if (kmat){ check_kmat(kmat.value(), coeffs.size()); } const std::size_t N = coeffs.size(); Eigen::ArrayXd m(N), epsilon_over_k(N), sigma_m(N), lambda_r(N), lambda_a(N); auto i = 0; for (const auto &coeff : coeffs) { m[i] = coeff.m; epsilon_over_k[i] = coeff.epsilon_over_k; sigma_m[i] = coeff.sigma_m; lambda_r[i] = coeff.lambda_r; lambda_a[i] = coeff.lambda_a; i++; } if (kmat){ return SAFTVRMieChainContributionTerms(m, epsilon_over_k, sigma_m, lambda_r, lambda_a, std::move(kmat.value())); } else{ auto mat = Eigen::ArrayXXd::Zero(N,N); 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; for (const auto &coeff : coeffs) { mustar2[i] = coeff.mustar2; nmu[i] = coeff.nmu; Qstar2[i] = coeff.Qstar2; nQ[i] = coeff.nQ; i++; } bool has_dipolar = ((mustar2*nmu).cwiseAbs().sum() == 0); bool has_quadrupolar = ((Qstar2*nQ).cwiseAbs().sum() == 0); if (!has_dipolar && !has_quadrupolar){ return std::nullopt; // No dipolar or quadrupolar contribution is present } else{ // The dispersive and hard chain initialization has already happened at this point return SAFTpolar::MultipolarContributionGrossVrabec(terms.m, terms.sigma_A, terms.epsilon_over_k, mustar2, nmu, Qstar2, nQ); } } 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; } // Checker for whether a polar term is present bool has_polar() const{ return polar.has_value(); } 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); auto fromArrayX = [](const Eigen::ArrayXd &x){std::valarray<double>n(x.size()); for (auto i =0; i < n.size(); ++i){ n[i] = x[i];} return n;}; auto fromArrayXX = [](const Eigen::ArrayXXd &x){ std::size_t N = x.rows(); std::vector<std::vector<double>> n; n.resize(N); for (auto i = 0; i < N; ++i){ n[i].resize(N); for (auto j = 0; j < N; ++j){ n[i][j] = x(i,j); } } return n; }; return nlohmann::json{ {"dmat", fromArrayXX(val.dmat)}, {"rhos", val.rhos}, {"rhoN", val.rhoN}, {"mbar", val.mbar}, {"xs", fromArrayX(val.xs)}, {"zeta", fromArrayX(val.zeta)}, {"zeta_x", val.zeta_x}, {"zeta_x_bar", val.zeta_x_bar}, {"alphar_mono", val.alphar_mono}, {"a1kB", val.a1kB}, {"a2kB2", val.a2kB2}, {"a3kB3", val.a3kB3}, {"alphar_chain", val.alphar_chain} }; } auto get_m() const { return terms.m; } auto get_sigma_Angstrom() const { return (terms.sigma_A).eval(); } auto get_sigma_m() const { return terms.sigma_A/1e10; } auto get_epsilon_over_k_K() const { return terms.epsilon_over_k; } auto get_kmat() const { return terms.kmat; } auto get_lambda_r() const { return terms.lambda_r; } auto get_lambda_a() const { return terms.lambda_a; } // template<typename VecType> // double max_rhoN(const double T, const VecType& mole_fractions) const { // auto N = mole_fractions.size(); // Eigen::ArrayX<double> d(N); // for (auto i = 0; i < N; ++i) { // d[i] = sigma_Angstrom[i] * (1.0 - 0.12 * exp(-3.0 * epsilon_over_k[i] / T)); // } // return 6 * 0.74 / EIGEN_PI / (mole_fractions*m*powvec(d, 3)).sum()*1e30; // particles/m^3 // } template<class VecType> auto R(const VecType& molefrac) const { return get_R_gas<decltype(molefrac[0])>(); } template<typename TTYPE, typename RhoType, typename VecType> auto alphar(const TTYPE& T, const RhoType& rhomolar, const VecType& mole_fractions) const { // First values for the Mie chain with dispersion (always included) error_if_expr(T); error_if_expr(rhomolar); auto vals = terms.get_core_calcs(T, rhomolar, mole_fractions); using type = std::common_type_t<TTYPE, RhoType, decltype(mole_fractions[0])>; type alphar = vals.alphar_mono + vals.alphar_chain; type packing_fraction = vals.zeta[3]; if (polar){ // polar term is present using mas = SAFTpolar::multipolar_argument_spec; auto visitor = [&T, &rhomolar, &mole_fractions, &packing_fraction](const auto& contrib) -> type { constexpr mas arg_spec = std::decay_t<decltype(contrib)>::arg_spec; if constexpr(arg_spec == mas::TK_rhoNA3_packingfraction_molefractions){ RhoType rho_A3 = rhomolar*N_A*1e-30; type alpha = contrib.eval(T, rho_A3, packing_fraction, mole_fractions).alpha; return alpha; } else if constexpr(arg_spec == mas::TK_rhoNm3_rhostar_molefractions){ RhoType rhoN_m3 = rhomolar*N_A; auto rhostar = contrib.get_rhostar(rhoN_m3, packing_fraction, mole_fractions); return contrib.eval(T, rhoN_m3, rhostar, mole_fractions).alpha; } else{ throw teqp::InvalidArgument("Don't know how to handle this kind of arguments in polar term"); } }; alphar += std::visit(visitor, polar.value()); } return forceeval(alphar); } }; inline 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_DA").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)); } 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, multipolar_rhostar_approach::use_packing_fraction); return SAFTVRMieMixture(SAFTVRMieMixture::build_chain(coeffs, kmat), std::move(polar)); } 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, multipolar_rhostar_approach::use_packing_fraction); return SAFTVRMieMixture(SAFTVRMieMixture::build_chain(coeffs, kmat), std::move(polar)); } if (polar_model == "GubbinsTwu+Luckas+GubbinsTwuRhostar"){ 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, multipolar_rhostar_approach::calculate_Gubbins_rhostar); return SAFTVRMieMixture(SAFTVRMieMixture::build_chain(coeffs, kmat), std::move(polar)); } if (polar_model == "GubbinsTwu+GubbinsTwu+GubbinsTwuRhostar"){ 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, multipolar_rhostar_approach::calculate_Gubbins_rhostar); return SAFTVRMieMixture(SAFTVRMieMixture::build_chain(coeffs, kmat), std::move(polar)); } 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