9#include "nlohmann/json.hpp"
47class SAFTVRMieLibrary {
48 std::map<std::string, SAFTVRMieCoeffs> coeffs;
51 insert_normal_fluid(
"Methane", 1.0000, 3.7412e-10, 153.36, 12.650, 6,
"Lafitte-JCP-2001");
52 insert_normal_fluid(
"Ethane", 1.4373, 3.7257e-10, 206.12, 12.400, 6,
"Lafitte-JCP-2001");
53 insert_normal_fluid(
"Propane", 1.6845, 3.9056e-10, 239.89, 13.006, 6,
"Lafitte-JCP-2001");
55 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) {
56 SAFTVRMieCoeffs coeff;
59 coeff.sigma_m = sigma_m;
60 coeff.epsilon_over_k = epsilon_over_k;
61 coeff.lambda_r = lambda_r;
62 coeff.lambda_a = lambda_a;
63 coeff.BibTeXKey = BibTeXKey;
64 coeffs.insert(std::pair<std::string, SAFTVRMieCoeffs>(name, coeff));
66 const auto& get_normal_fluid(
const std::string& name) {
67 auto it = coeffs.find(name);
68 if (it != coeffs.end()) {
72 throw std::invalid_argument(
"Bad name:" + name);
75 auto get_coeffs(
const std::vector<std::string>& names){
76 std::vector<SAFTVRMieCoeffs> c;
78 c.push_back(get_normal_fluid(n));
89 const Eigen::Matrix<double, 7, 7> phi{(Eigen::Matrix<double, 7, 7>() <<
90 7.5365557, -359.44, 1550.9, -1.19932, -1911.28, 9236.9, 10,
91 -37.60463, 1825.6, -5070.1, 9.063632, 21390.175, -129430, 10,
92 71.745953, -3168.0, 6534.6, -17.9482, -51320.7, 357230, 0.57,
93 -46.83552, 1884.2, -3288.7, 11.34027, 37064.54, -315530, -6.7,
94 -2.467982, -0.82376, -2.7171, 20.52142, 1103.742, 1390.2, -8,
95 -0.50272, -3.1935, 2.0883, -56.6377, -3264.61, -4518.2, 0,
96 8.0956883, 3.7090, 0, 40.53683, 2556.181, 4241.6, 0 ).finished()};
99 const Eigen::Matrix<double, 4, 4> A{(Eigen::Matrix<double, 4, 4>() <<
100 0.81096, 1.7888, -37.578, 92.284,
101 1.0205, -19.341, 151.26, -463.50,
102 -1.9057, 22.845, -228.14, 973.92,
103 1.0885, -6.1962, 106.98, -677.64).finished()};
106 auto get_lambda_k_ij(
const Eigen::ArrayXd& lambda_k)
const{
107 Eigen::ArrayXXd mat(
N,
N);
108 for (
auto i = 0; i < lambda_k.size(); ++i){
109 for (
auto j = i; j < lambda_k.size(); ++j){
110 mat(i,j) = 3 + sqrt((lambda_k(i)-3)*(lambda_k(j)-3));
118 auto get_C_ij()
const{
119 Eigen::ArrayXXd C(
N,
N);
120 for (
auto i = 0U; i <
N; ++i){
121 for (
auto j = i; j <
N; ++j){
122 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)));
130 auto get_fkij()
const{
131 std::vector<Eigen::ArrayXXd> f_(8);
132 for (
auto k = 1; k < 8; ++k){
135 for (
auto k = 1; k < 8; ++k){
136 auto phik = phi.col(k-1);
137 Eigen::ArrayXXd num(
N,
N), den(
N,
N); num.setZero(), den.setZero();
138 for (
auto n = 0; n < 4; ++n){
141 for (
auto n = 4; n < 7; ++n){
144 f_[k] = num/(1 + den);
150 auto get_sigma_ij()
const{
151 Eigen::ArrayXXd sigma(
N,
N);
152 for (
auto i = 0U; i <
N; ++i){
153 for (
auto j = i; j <
N; ++j){
155 sigma(j,i) = sigma(i,j);
164 auto get_epsilon_ij()
const{
167 Eigen::ArrayXXd eps_(
N,
N);
168 for (
auto i = 0U; i <
N; ++i){
169 for (
auto j = i; j <
N; ++j){
171 eps_(j,i) = eps_(i,j);
177 Eigen::ArrayXXd eps_(
N,
N);
178 for (
auto i = 0U; i <
N; ++i){
179 for (
auto j = i; j <
N; ++j){
181 eps_(j,i) = eps_(i,j);
187 throw std::invalid_argument(
"epsilon_ij_flag is invalid");
192 if (sizes.maxCoeff() != sizes.minCoeff()){
199 auto get_cij(
const Eigen::ArrayXXd& lambdaij)
const{
200 std::vector<Eigen::ArrayXXd> cij(4);
201 for (
auto n = 0; n < 4; ++n){
204 for (
auto i = 0U; i <
N; ++i){
205 for (
auto j = i; j <
N; ++j){
206 using CV = Eigen::Vector<double, 4>;
207 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()};
208 auto c1234 = (A*b).eval();
209 cij[0](i,j) = c1234(0);
210 cij[1](i,j) = c1234(1);
211 cij[2](i,j) = c1234(2);
212 cij[3](i,j) = c1234(3);
219 auto get_canij()
const{
223 auto get_c2anij()
const{
227 auto get_crnij()
const{
231 auto get_c2rnij()
const{
235 auto get_carnij()
const{
239 EpsilonijFlags get_epsilon_ij(
const std::optional<nlohmann::json>& flags){
241 const nlohmann::json& j = flags.value();
242 if (j.contains(
"epsilon_ij")){
255 const Eigen::Index
N;
262 const std::vector<Eigen::ArrayXXd>
fkij;
265 const Eigen::ArrayXd&
m,
267 const Eigen::ArrayXd& sigma_m,
270 const Eigen::ArrayXXd&
kmat,
271 const std::optional<nlohmann::json> & flags = std::nullopt)
289 template<
typename RType>
296 template <
typename TType>
299 auto EPS = std::numeric_limits<
decltype(
getbaseval(T))>::epsilon();
305 auto fgh = [&kappa, &lambda_r_, &lambda_a_, &T, &EPS](
auto j){
306 auto jlr =
pow(j, lambda_r_), jla =
pow(j, lambda_a_);
307 auto u = kappa*(jlr - jla);
308 auto uprime = kappa*(lambda_r_*jlr - lambda_a_*jla)/j;
309 auto uprime2 = kappa*(lambda_r_*(lambda_r_-1.0)*jlr - lambda_a_*(lambda_a_-1.0)*jla)/(j*j);
313 for (
auto counter = 0; counter <= 3; ++counter){
315 auto [R, Rprime, Rprime2] = fgh(j);
316 auto denominator = 2.0*Rprime*Rprime-R*Rprime2;
320 j -= 2.0*R*Rprime/denominator;
361 template <
typename TType>
362 TType
get_dii(std::size_t i,
const TType &T)
const{
363 std::function<TType(TType)> integrand = [
this, i, &T](
const TType& r){
370 auto d =
forceeval(rcut + integral_contribution);
378 template <
typename TType>
380 Eigen::Array<TType, Eigen::Dynamic, Eigen::Dynamic> d(
N,
N);
382 for (
auto i = 0U; i <
N; ++i){
386 for (
auto i = 0U; i <
N; ++i){
387 for (
auto j = i+1; j <
N; ++j){
388 d(i,j) = (d(i,i) + d(j,j))/2.0;
395 template <
typename TType,
typename RhoType,
typename VecType>
396 auto get_core_calcs(
const TType& T,
const RhoType& rhomolar,
const VecType& molefracs)
const{
398 if (molefracs.size() !=
N){
399 throw teqp::InvalidArgument(
"Length of molefracs of "+std::to_string(molefracs.size()) +
" does not match the model size of"+std::to_string(
N));
402 using FracType = std::decay_t<
decltype(molefracs[0])>;
403 using NumType = std::common_type_t<TType, RhoType, FracType>;
412 auto xs =
forceeval((
m*molefracs/mbar).eval());
414 constexpr double MY_PI =
static_cast<double>(EIGEN_PI);
417 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])>>;
418 using DType = std::common_type_t<std::decay_t<TType>, std::decay_t<
decltype(molefracs[0])>, std::decay_t<
decltype(
m[0])>>;
419 Eigen::Array<TRHOType, 4, 1> zeta;
420 Eigen::Array<DType, 4, 1> D;
421 for (
auto l = 0; l < 4; ++l){
423 for (
auto i = 0U; i <
N; ++i){
424 summer += xs(i)*
powi(dmat(i,i), l);
430 NumType summer_zeta_x = 0.0;
431 TRHOType summer_zeta_x_bar = 0.0;
432 for (
auto i = 0U; i <
N; ++i){
433 for (
auto j = 0U; j <
N; ++j){
434 summer_zeta_x += xs(i)*xs(j)*
powi(dmat(i,j), 3)*rhos;
435 summer_zeta_x_bar += xs(i)*xs(j)*
powi(
sigma_ij(i,j), 3);
439 auto zeta_x =
forceeval(pi6*summer_zeta_x);
440 auto zeta_x_bar =
forceeval(pi6*rhos*summer_zeta_x_bar);
448 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));
454 auto dmat3 = dmat.array().cube().eval();
459 NumType alphar_chain = 0.0;
461 NumType K_HS =
get_KHS(zeta_x);
464 for (
auto i = 0U; i <
N; ++i){
465 for (
auto j = i; j <
N; ++j){
466 NumType x_0_ij =
sigma_ij(i,j)/dmat(i, j);
472 auto I = [&x_0_ij](
double lambda_ij){
473 return forceeval(-(
pow(x_0_ij, 3-lambda_ij)-1.0)/(lambda_ij-3.0));
475 auto J = [&x_0_ij](
double lambda_ij){
476 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)));
484 auto one_term = [
this, &x_0_ij, &I, &J, &zeta_x, &
X](
double lambda_ij,
const NumType& zeta_x_eff){
486 pow(x_0_ij, lambda_ij)*(
487 this->
get_Bhatij(zeta_x,
X, I(lambda_ij), J(lambda_ij))
497 NumType a1ij = 2.0*MY_PI*rhos*dmat3(i,j)*
epsilon_ij(i,j)*
C_ij(i,j)*(
501 NumType contribution = xs(i)*xs(j)*a1ij;
502 double factor = (i == j) ? 1.0 : 2.0;
503 a1kB += contribution*factor;
516 NumType chi_ij =
fkij[1](i,j)*zeta_x_bar +
fkij[2](i,j)*zeta_x_bar5 +
fkij[3](i,j)*zeta_x_bar8;
523 NumType contributiona2 = xs(i)*xs(j)*a2ij;
524 a2kB2 += contributiona2*factor;
532 NumType contributiona3 = xs(i)*xs(j)*a3ij;
533 a3kB3 += contributiona3*factor;
541 auto gdHSii = exp(k0 + k1*x_0_ij + k2*
POW2(x_0_ij) + k3*
POW3(x_0_ij));
548 auto g1_term = [&one_term](
double lambda_ij,
const NumType& zeta_x_eff){
549 return forceeval(lambda_ij*one_term(lambda_ij, zeta_x_eff));
556 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){
557 auto I_ = I(lambda_ij);
558 auto J_ = J(lambda_ij);
559 auto rhosda1Sdrhos = this->
get_rhoda1Shatijdrho(zeta_x, zeta_x_eff, dzetaxeff_dzetax, lambda_ij);
561 return forceeval(
pow(x_0_ij, lambda_ij)*(rhosda1Sdrhos + rhosdBdrhos));
564 auto da1iidrhos_term =
C_ij(i,j)*(
565 rhosda1iidrhos_term(
lambda_a_ij(i,i), zeta_x_eff_a, dzeta_x_eff_dzetax_a, Bhatij_a)
566 -rhosda1iidrhos_term(
lambda_r_ij(i,i), zeta_x_eff_r, dzeta_x_eff_dzetax_r, Bhatij_r)
568 auto g1ii = 3.0*da1iidrhos_term + g1_noderivterm;
575 auto g2_noderivterm = -
POW2(
C_ij(i,i))*K_HS*(
581 auto da2iidrhos_term = 0.5*
POW2(
C_ij(i,j))*(
587 rhosda1iidrhos_term(2.0*
lambda_a_ij(i,i), zeta_x_eff_2a, dzeta_x_eff_dzetax_2a, Bhatij_2a)
588 -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)
589 +rhosda1iidrhos_term(2.0*
lambda_r_ij(i,i), zeta_x_eff_2r, dzeta_x_eff_dzetax_2r, Bhatij_2r)
592 auto g2MCAij = 3.0*da2iidrhos_term + g2_noderivterm;
595 auto theta = exp(betaepsilon)-1.0;
596 auto phi7 = phi.col(6);
597 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));
598 auto g2ii = (1.0+gamma_cij)*g2MCAij;
600 NumType giiMie = gdHSii*exp((betaepsilon*g1ii +
POW2(betaepsilon)*g2ii)/gdHSii);
601 alphar_chain -= molefracs[i]*(
m[i]-1.0)*log(giiMie);
608 auto alphar_mono =
forceeval(mbar*(ahs + a1kB/T + a2kB2/(T*T) + a3kB3/(T*T*T)));
610 using dmat_t =
decltype(dmat);
611 using rhos_t =
decltype(rhos);
612 using rhoN_t =
decltype(rhoN);
613 using mbar_t =
decltype(mbar);
614 using xs_t =
decltype(xs);
615 using zeta_t =
decltype(zeta);
616 using zeta_x_t =
decltype(zeta_x);
617 using zeta_x_bar_t =
decltype(zeta_x_bar);
618 using alphar_mono_t =
decltype(alphar_mono);
619 using a1kB_t =
decltype(a1kB);
620 using a2kB2_t =
decltype(a2kB2);
621 using a3kB3_t =
decltype(a3kB3);
622 using alphar_chain_t =
decltype(alphar_chain);
631 zeta_x_bar_t zeta_x_bar;
632 alphar_mono_t alphar_mono;
636 alphar_chain_t alphar_chain;
638 return vals{dmat, rhos, rhoN, mbar, xs, zeta, zeta_x, zeta_x_bar, alphar_mono, a1kB, a2kB2, a3kB3, alphar_chain};
642 template<
typename RhoType>
644 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));
652 template<
typename RhoType>
654 auto num = -4.0*
POW3(zeta_x - 1.0)*(
POW2(zeta_x) - 5.0*zeta_x - 2.0);
655 auto den =
POW2(
POW4(zeta_x) - 4.0*
POW3(zeta_x) + 4.0*
POW2(zeta_x) + 4.0*zeta_x + 1.0);
660 template<
typename RhoType,
typename ZetaType,
typename DType>
661 auto get_a_HS(
const RhoType& rhos,
const Eigen::Array<ZetaType, 4, 1>& zeta,
const Eigen::Array<DType, 4, 1>& D)
const{
662 constexpr double MY_PI =
static_cast<double>(EIGEN_PI);
691 auto Upsilon = 1.0 - zeta[3];
693 3.0*D[1]/D[0]*zeta[2]/Upsilon
694 + D[2]*D[2]*zeta[2]/(D[3]*D[0]*Upsilon*Upsilon)
696 + (D[2]*D[2]*D[2])/(D[3]*D[3]*D[0])*log(Upsilon)
700 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])));
712 template<
typename ZetaType,
typename IJ>
713 auto get_Bhatij(
const ZetaType& zeta_x,
const ZetaType& one_minus_zeta_x3,
const IJ& I,
const IJ& J)
const{
715 (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
731 template<
typename ZetaType,
typename IJ>
732 auto get_rhodBijdrho(
const ZetaType& zeta_x,
const ZetaType& ,
const IJ& I,
const IJ& J,
const ZetaType& Bhatij)
const{
733 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));
734 return forceeval(Bhatij + dBhatdzetax*zeta_x);
748 template<
typename ZetaType>
751 -1.0/(lambda_ij-3.0)*(1.0-zeta_x_eff/2.0)/
POW3(
forceeval(1.0-zeta_x_eff))
766 template<
typename ZetaType>
767 auto get_rhoda1Shatijdrho(
const ZetaType& zeta_x,
const ZetaType& zeta_x_eff,
const ZetaType& dzetaxeffdzetax,
double lambda_ij)
const{
768 auto zetaxda1Shatdzetax = ((2.0*zeta_x_eff - 5.0)*dzetaxeffdzetax)/(2.0*(lambda_ij-3)*
POW4(zeta_x_eff-1.0))*zeta_x;
779 std::vector<std::string> names, bibtex;
782 static void check_kmat(
const Eigen::ArrayXXd& kmat, Eigen::Index N) {
783 if (kmat.size() == 0){
786 if (kmat.cols() != kmat.rows()) {
789 if (kmat.cols() != N) {
790 throw teqp::InvalidArgument(
"kmat needs to be a square matrix the same size as the number of components");
793 static auto get_coeffs_from_names(
const std::vector<std::string> &names){
794 SAFTVRMieLibrary library;
795 return library.get_coeffs(names);
797 auto get_names(
const std::vector<SAFTVRMieCoeffs> &coeffs){
798 std::vector<std::string> names_;
799 for (
auto c : coeffs){
800 names_.push_back(c.name);
804 auto get_bibtex(
const std::vector<SAFTVRMieCoeffs> &coeffs){
805 std::vector<std::string> keys_;
806 for (
auto c : coeffs){
807 keys_.push_back(c.BibTeXKey);
812 static auto build_chain(
const std::vector<SAFTVRMieCoeffs> &coeffs,
const std::optional<Eigen::ArrayXXd>& kmat,
const std::optional<nlohmann::json>& flags = std::nullopt){
814 check_kmat(kmat.value(), coeffs.size());
816 const std::size_t N = coeffs.size();
817 Eigen::ArrayXd m(N), epsilon_over_k(N), sigma_m(N), lambda_r(N), lambda_a(N);
819 for (
const auto &coeff : coeffs) {
821 epsilon_over_k[i] = coeff.epsilon_over_k;
822 sigma_m[i] = coeff.sigma_m;
823 lambda_r[i] = coeff.lambda_r;
824 lambda_a[i] = coeff.lambda_a;
831 auto mat = Eigen::ArrayXXd::Zero(N,N);
837 SAFTVRMieNonpolarMixture(
const std::vector<std::string> &names,
const std::optional<Eigen::ArrayXXd>& kmat = std::nullopt,
const std::optional<nlohmann::json>&flags = std::nullopt) :
SAFTVRMieNonpolarMixture(get_coeffs_from_names(names), kmat, flags){};
838 SAFTVRMieNonpolarMixture(
const std::vector<SAFTVRMieCoeffs> &coeffs,
const std::optional<Eigen::ArrayXXd> &kmat = std::nullopt,
const std::optional<nlohmann::json>&flags = std::nullopt) : names(
get_names(coeffs)), bibtex(get_bibtex(coeffs)), terms(
build_chain(coeffs, kmat, flags)) {};
843 auto chain_factory(
const std::vector<SAFTVRMieCoeffs> &coeffs,
const std::optional<Eigen::ArrayXXd>& kmat){
848 auto get_core_calcs(
double T,
double rhomolar,
const Eigen::ArrayXd& mole_fractions)
const {
851 auto fromArrayX = [](
const Eigen::ArrayXd &x){std::valarray<double>n(x.size());
for (
auto i = 0U; i < n.size(); ++i){ n[i] = x[i];}
return n;};
852 auto fromArrayXX = [](
const Eigen::ArrayXXd &x){
853 std::size_t N = x.rows();
854 std::vector<std::vector<double>> n; n.resize(N);
855 for (
auto i = 0U; i < N; ++i){
857 for (
auto j = 0U; j < N; ++j){
863 return nlohmann::json{
864 {
"dmat", fromArrayXX(val.dmat)},
868 {
"xs", fromArrayX(val.xs)},
869 {
"zeta", fromArrayX(val.zeta)},
870 {
"zeta_x", val.zeta_x},
871 {
"zeta_x_bar", val.zeta_x_bar},
872 {
"alphar_mono", val.alphar_mono},
874 {
"a2kB2", val.a2kB2},
875 {
"a3kB3", val.a3kB3},
876 {
"alphar_chain", val.alphar_chain}
901 template<
class VecType>
902 auto R(
const VecType& molefrac)
const {
906 template<
typename TTYPE,
typename RhoType,
typename VecType>
907 auto alphar(
const TTYPE& T,
const RhoType& rhomolar,
const VecType& mole_fractions)
const {
911 using type = std::common_type_t<TTYPE, RhoType,
decltype(mole_fractions[0])>;
912 type
alphar = vals.alphar_mono + vals.alphar_chain;
924 std::vector<std::string> names, bibtex;
926 const std::optional<SAFTpolar::multipolar_contributions_variant> polar;
928 static void check_kmat(
const Eigen::ArrayXXd& kmat, Eigen::Index N) {
929 if (kmat.size() == 0){
932 if (kmat.cols() != kmat.rows()) {
935 if (kmat.cols() != N) {
936 throw teqp::InvalidArgument(
"kmat needs to be a square matrix the same size as the number of components");
939 static auto get_coeffs_from_names(
const std::vector<std::string> &names){
940 SAFTVRMieLibrary library;
941 return library.get_coeffs(names);
943 auto get_names(
const std::vector<SAFTVRMieCoeffs> &coeffs){
944 std::vector<std::string> names_;
945 for (
auto c : coeffs){
946 names_.push_back(c.name);
950 auto get_bibtex(
const std::vector<SAFTVRMieCoeffs> &coeffs){
951 std::vector<std::string> keys_;
952 for (
auto c : coeffs){
953 keys_.push_back(c.BibTeXKey);
958 static auto build_chain(
const std::vector<SAFTVRMieCoeffs> &coeffs,
const std::optional<Eigen::ArrayXXd>& kmat,
const std::optional<nlohmann::json>& flags = std::nullopt){
960 check_kmat(kmat.value(), coeffs.size());
962 const std::size_t N = coeffs.size();
963 Eigen::ArrayXd m(N), epsilon_over_k(N), sigma_m(N), lambda_r(N), lambda_a(N);
965 for (
const auto &coeff : coeffs) {
967 epsilon_over_k[i] = coeff.epsilon_over_k;
968 sigma_m[i] = coeff.sigma_m;
969 lambda_r[i] = coeff.lambda_r;
970 lambda_a[i] = coeff.lambda_a;
977 auto mat = Eigen::ArrayXXd::Zero(N,N);
981 auto build_polar(
const std::vector<SAFTVRMieCoeffs> &coeffs) ->
decltype(this->polar){
982 Eigen::ArrayXd mustar2(coeffs.size()), nmu(coeffs.size()), Qstar2(coeffs.size()), nQ(coeffs.size());
984 for (
const auto &coeff : coeffs) {
985 mustar2[i] = coeff.mustar2;
987 Qstar2[i] = coeff.Qstar2;
991 bool has_dipolar = ((mustar2*nmu).cwiseAbs().sum() == 0);
992 bool has_quadrupolar = ((Qstar2*nQ).cwiseAbs().sum() == 0);
993 if (!has_dipolar && !has_quadrupolar){
1003 SAFTVRMieMixture(
const std::vector<std::string> &names,
const std::optional<Eigen::ArrayXXd>& kmat = std::nullopt,
const std::optional<nlohmann::json>&flags = std::nullopt) :
SAFTVRMieMixture(get_coeffs_from_names(names), kmat, flags){};
1004 SAFTVRMieMixture(
const std::vector<SAFTVRMieCoeffs> &coeffs,
const std::optional<Eigen::ArrayXXd> &kmat = std::nullopt,
const std::optional<nlohmann::json>&flags = std::nullopt) : names(
get_names(coeffs)), bibtex(get_bibtex(coeffs)), terms(
build_chain(coeffs, kmat, flags)), polar(
build_polar(coeffs)) {};
1011 auto chain_factory(
const std::vector<SAFTVRMieCoeffs> &coeffs,
const std::optional<Eigen::ArrayXXd>& kmat){
1021 auto get_core_calcs(
double T,
double rhomolar,
const Eigen::ArrayXd& mole_fractions)
const {
1024 auto fromArrayX = [](
const Eigen::ArrayXd &x){std::valarray<double>n(x.size());
for (
auto i = 0U; i < n.size(); ++i){ n[i] = x[i];}
return n;};
1025 auto fromArrayXX = [](
const Eigen::ArrayXXd &x){
1026 std::size_t N = x.rows();
1027 std::vector<std::vector<double>> n; n.resize(N);
1028 for (
auto i = 0U; i < N; ++i){
1030 for (
auto j = 0U; j < N; ++j){
1036 return nlohmann::json{
1037 {
"dmat", fromArrayXX(val.dmat)},
1041 {
"xs", fromArrayX(val.xs)},
1042 {
"zeta", fromArrayX(val.zeta)},
1043 {
"zeta_x", val.zeta_x},
1044 {
"zeta_x_bar", val.zeta_x_bar},
1045 {
"alphar_mono", val.alphar_mono},
1047 {
"a2kB2", val.a2kB2},
1048 {
"a3kB3", val.a3kB3},
1049 {
"alphar_chain", val.alphar_chain}
1074 template<
class VecType>
1075 auto R(
const VecType& molefrac)
const {
1079 template<
typename TTYPE,
typename RhoType,
typename VecType>
1080 auto alphar(
const TTYPE& T,
const RhoType& rhomolar,
const VecType& mole_fractions)
const {
1084 using type = std::common_type_t<TTYPE, RhoType,
decltype(mole_fractions[0])>;
1085 type
alphar = vals.alphar_mono + vals.alphar_chain;
1086 type packing_fraction = vals.zeta[3];
1090 auto visitor = [&T, &rhomolar, &mole_fractions, &packing_fraction](
const auto& contrib) -> type {
1092 constexpr mas arg_spec = std::decay_t<
decltype(contrib)>::arg_spec;
1093 if constexpr(arg_spec == mas::TK_rhoNA3_packingfraction_molefractions){
1094 RhoType rho_A3 = rhomolar*N_A*1e-30;
1095 type alpha = contrib.eval(T, rho_A3, packing_fraction, mole_fractions).alpha;
1098 else if constexpr(arg_spec == mas::TK_rhoNm3_rhostar_molefractions){
1099 RhoType rhoN_m3 = rhomolar*N_A;
1100 auto rhostar = contrib.get_rhostar(rhoN_m3, packing_fraction, mole_fractions);
1101 return contrib.eval(T, rhoN_m3, rhostar, mole_fractions).alpha;
1107 alphar += std::visit(visitor, polar.value());
1117 std::optional<Eigen::ArrayXXd> kmat;
1118 if (spec.contains(
"kmat") && spec.at(
"kmat").is_array() && spec.at(
"kmat").size() > 0){
1122 if (spec.contains(
"names")){
1123 std::vector<std::string> names = spec[
"names"];
1124 if (kmat &&
static_cast<std::size_t
>(kmat.value().rows()) != names.size()){
1125 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()));
1127 return klass(names, kmat);
1129 else if (spec.contains(
"coeffs")){
1130 std::vector<SAFTVRMieCoeffs> coeffs;
1131 for (
auto j : spec[
"coeffs"]) {
1133 c.
name = j.at(
"name");
1135 c.
sigma_m = (j.contains(
"sigma_m")) ? j.at(
"sigma_m").get<
double>() : j.at(
"sigma_Angstrom").get<
double>()/1e10;
1140 coeffs.push_back(c);
1142 if (kmat &&
static_cast<std::size_t
>(kmat.value().rows()) != coeffs.size()){
1143 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()));
1145 return klass(klass::build_chain(coeffs, kmat), coeffs);
1148 throw std::invalid_argument(
"you must provide names or coeffs, but not both");
1154 std::optional<Eigen::ArrayXXd> kmat;
1155 if (spec.contains(
"kmat") && spec.at(
"kmat").is_array() && spec.at(
"kmat").size() > 0){
1159 if (spec.contains(
"names")){
1160 std::vector<std::string> names = spec[
"names"];
1161 if (kmat &&
static_cast<std::size_t
>(kmat.value().rows()) != names.size()){
1162 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()));
1166 else if (spec.contains(
"coeffs")){
1167 bool something_polar =
false;
1168 std::vector<SAFTVRMieCoeffs> coeffs;
1169 for (
auto j : spec[
"coeffs"]) {
1171 c.
name = j.at(
"name");
1173 c.
sigma_m = (j.contains(
"sigma_m")) ? j.at(
"sigma_m").get<
double>() : j.at(
"sigma_Angstrom").get<
double>()/1e10;
1180 if (j.contains(
"(mu^*)^2") && j.contains(
"nmu")){
1182 c.
nmu = j.at(
"nmu");
1183 something_polar =
true;
1185 if (j.contains(
"(Q^*)^2") && j.contains(
"nQ")){
1186 c.
Qstar2 = j.at(
"(Q^*)^2");
1188 something_polar =
true;
1190 if (j.contains(
"Q_Cm2") || j.contains(
"Q_DA") || j.contains(
"mu_Cm") || j.contains(
"mu_D")){
1191 something_polar =
true;
1193 coeffs.push_back(c);
1195 if (kmat &&
static_cast<std::size_t
>(kmat.value().rows()) != coeffs.size()){
1196 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()));
1199 if (!something_polar){
1205 std::string polar_model =
"GrossVrabec";
1206 if (spec.contains(
"polar_model")){
1207 polar_model = spec[
"polar_model"];
1209 std::optional<nlohmann::json> SAFTVRMie_flags = std::nullopt;
1210 if (spec.contains(
"SAFTVRMie_flags")){
1211 SAFTVRMie_flags = spec[
"SAFTVRMie_flags"];
1213 std::optional<nlohmann::json> polar_flags = std::nullopt;
1214 if (spec.contains(
"polar_flags")){
1215 polar_flags = spec[
"polar_flags"];
1219 polar_flags = nlohmann::json{{
"approach",
"use_packing_fraction"}};
1224 const double D_to_Cm = 3.33564e-30;
1225 const double mustar2factor = 1.0/(4*
static_cast<double>(EIGEN_PI)*8.8541878128e-12*1.380649e-23);
1226 const double Qstar2factor = 1.0/(4*
static_cast<double>(EIGEN_PI)*8.8541878128e-12*1.380649e-23);
1227 auto N = coeffs.size();
1228 Eigen::ArrayXd ms(N), epsks(N), sigma_ms(N), mu_Cm(N), Q_Cm2(N), nQ(N), nmu(N);
1230 for (
auto j : spec[
"coeffs"]) {
1231 double m = j.at(
"m");
1232 double sigma_m = (j.contains(
"sigma_m")) ? j.at(
"sigma_m").get<
double>() : j.at(
"sigma_Angstrom").get<
double>()/1e10;
1233 double epsilon_over_k = j.at(
"epsilon_over_k");
1234 auto get_dipole_Cm = [&]() ->
double {
1235 if (j.contains(
"(mu^*)^2") && j.contains(
"nmu")){
1237 double mustar2 = j.at(
"(mu^*)^2");
1238 return sqrt(mustar2*(m*epsilon_over_k*
pow(sigma_m, 3))/mustar2factor);
1240 else if (j.contains(
"mu_Cm")){
1241 return j.at(
"mu_Cm");
1243 else if (j.contains(
"mu_D")){
1244 return j.at(
"mu_D").get<
double>()*D_to_Cm;
1250 auto get_quadrupole_Cm2 = [&]() ->
double{
1251 if (j.contains(
"(Q^*)^2") && j.contains(
"nQ")){
1253 double Qstar2 = j.at(
"(Q^*)^2");
1254 return sqrt(Qstar2*(m*epsilon_over_k*
pow(sigma_m, 5))/Qstar2factor);
1256 else if (j.contains(
"Q_Cm2")){
1257 return j.at(
"Q_Cm2");
1259 else if (j.contains(
"Q_DA")){
1260 return j.at(
"Q_DA").get<
double>()*D_to_Cm/1e10;
1266 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();
1267 nmu(i) = (j.contains(
"nmu") ? j[
"nmu"].get<
double>() : 0.0);
1268 nQ(i) = (j.contains(
"nQ") ? j[
"nQ"].get<
double>() : 0.0);
1271 nlohmann::json SAFTVRMieFlags;
1274 auto EPSKIJ = chain.get_EPSKIJ_K_matrix();
1275 auto SIGMAIJ = chain.get_SIGMAIJ_m_matrix();
1277 using namespace SAFTpolar;
1278 if (polar_model ==
"GrossVrabec"){
1279 auto mustar2 = (mustar2factor*mu_Cm.pow(2)/(ms*epsks*sigma_ms.pow(3))).eval();
1280 auto Qstar2 = (Qstar2factor*Q_Cm2.pow(2)/(ms*epsks*sigma_ms.pow(5))).eval();
1284 if (polar_model ==
"GubbinsTwu+Luckas"){
1285 using MCGTL = MultipolarContributionGubbinsTwu<LuckasJIntegral, LuckasKIntegral>;
1286 auto mubar2 = (mustar2factor*mu_Cm.pow(2)/(epsks*sigma_ms.pow(3))).eval();
1287 auto Qbar2 = (Qstar2factor*Q_Cm2.pow(2)/(epsks*sigma_ms.pow(5))).eval();
1288 auto polar = MCGTL(sigma_ms, epsks, mubar2, Qbar2, multipolar_rhostar_approach::use_packing_fraction);
1291 if (polar_model ==
"GubbinsTwu+GubbinsTwu"){
1292 using MCGG = MultipolarContributionGubbinsTwu<GubbinsTwuJIntegral, GubbinsTwuKIntegral>;
1293 auto mubar2 = (mustar2factor*mu_Cm.pow(2)/(epsks*sigma_ms.pow(3))).eval();
1294 auto Qbar2 = (Qstar2factor*Q_Cm2.pow(2)/(epsks*sigma_ms.pow(5))).eval();
1295 auto polar = MCGG(sigma_ms, epsks, mubar2, Qbar2, multipolar_rhostar_approach::use_packing_fraction);
1298 if (polar_model ==
"GubbinsTwu+Gottschalk"){
1299 using MCGG = MultipolarContributionGubbinsTwu<GottschalkJIntegral, GottschalkKIntegral>;
1300 auto mubar2 = (mustar2factor*mu_Cm.pow(2)/(epsks*sigma_ms.pow(3))).eval();
1301 auto Qbar2 = (Qstar2factor*Q_Cm2.pow(2)/(epsks*sigma_ms.pow(5))).eval();
1302 auto polar = MCGG(sigma_ms, epsks, mubar2, Qbar2, multipolar_rhostar_approach::use_packing_fraction);
1306 if (polar_model ==
"GrayGubbins+GubbinsTwu"){
1307 using MCGG = MultipolarContributionGrayGubbins<GubbinsTwuJIntegral, GubbinsTwuKIntegral>;
1308 auto polar = MCGG(sigma_ms, epsks, SIGMAIJ, EPSKIJ, mu_Cm, Q_Cm2, polar_flags);
1316 if (polar_model ==
"GrayGubbins+Luckas"){
1317 using MCGG = MultipolarContributionGrayGubbins<LuckasJIntegral, LuckasKIntegral>;
1318 auto polar = MCGG(sigma_ms, epsks, SIGMAIJ, EPSKIJ, mu_Cm, Q_Cm2, polar_flags);
1323 if (polar_model ==
"GubbinsTwu+Luckas+GubbinsTwuRhostar"){
1324 using MCGTL = MultipolarContributionGubbinsTwu<LuckasJIntegral, LuckasKIntegral>;
1325 auto mubar2 = (mustar2factor*mu_Cm.pow(2)/(epsks*sigma_ms.pow(3))).eval();
1326 auto Qbar2 = (Qstar2factor*Q_Cm2.pow(2)/(epsks*sigma_ms.pow(5))).eval();
1327 auto polar = MCGTL(sigma_ms, epsks, mubar2, Qbar2, multipolar_rhostar_approach::calculate_Gubbins_rhostar);
1330 if (polar_model ==
"GubbinsTwu+GubbinsTwu+GubbinsTwuRhostar"){
1331 using MCGG = MultipolarContributionGubbinsTwu<GubbinsTwuJIntegral, GubbinsTwuKIntegral>;
1332 auto mubar2 = (mustar2factor*mu_Cm.pow(2)/(epsks*sigma_ms.pow(3))).eval();
1333 auto Qbar2 = (Qstar2factor*Q_Cm2.pow(2)/(epsks*sigma_ms.pow(5))).eval();
1334 auto polar = MCGG(sigma_ms, epsks, mubar2, Qbar2, multipolar_rhostar_approach::calculate_Gubbins_rhostar);
1341 throw std::invalid_argument(
"you must provide names or coeffs, but not both");
A class used to evaluate mixtures using the SAFT-VR-Mie model.
SAFTVRMieMixture & operator=(const SAFTVRMieMixture &)=delete
auto get_BibTeXKeys() const
auto get_lambda_r() const
auto get_sigma_Angstrom() const
auto get_EPSKIJ_matrix() const
auto get_lambda_a() const
auto get_core_calcs(double T, double rhomolar, const Eigen::ArrayXd &mole_fractions) const
const auto & get_polar() const
auto R(const VecType &molefrac) const
auto get_SIGMAIJ_matrix() const
auto alphar(const TTYPE &T, const RhoType &rhomolar, const VecType &mole_fractions) const
SAFTVRMieMixture(const std::vector< SAFTVRMieCoeffs > &coeffs, const std::optional< Eigen::ArrayXXd > &kmat=std::nullopt, const std::optional< nlohmann::json > &flags=std::nullopt)
SAFTVRMieMixture(SAFTVRMieChainContributionTerms &&terms, const std::vector< SAFTVRMieCoeffs > &coeffs, std::optional< SAFTpolar::multipolar_contributions_variant > &&polar=std::nullopt)
auto build_polar(const std::vector< SAFTVRMieCoeffs > &coeffs) -> decltype(this->polar)
auto chain_factory(const std::vector< SAFTVRMieCoeffs > &coeffs, const std::optional< Eigen::ArrayXXd > &kmat)
SAFTVRMieMixture(const std::vector< std::string > &names, const std::optional< Eigen::ArrayXXd > &kmat=std::nullopt, const std::optional< nlohmann::json > &flags=std::nullopt)
const auto & get_terms() const
auto get_epsilon_over_k_K() const
static auto build_chain(const std::vector< SAFTVRMieCoeffs > &coeffs, const std::optional< Eigen::ArrayXXd > &kmat, const std::optional< nlohmann::json > &flags=std::nullopt)
A class used to evaluate mixtures using the SAFT-VR-Mie model.
SAFTVRMieNonpolarMixture(SAFTVRMieChainContributionTerms &&terms, const std::vector< SAFTVRMieCoeffs > &coeffs)
SAFTVRMieNonpolarMixture(const std::vector< SAFTVRMieCoeffs > &coeffs, const std::optional< Eigen::ArrayXXd > &kmat=std::nullopt, const std::optional< nlohmann::json > &flags=std::nullopt)
auto get_BibTeXKeys() const
auto alphar(const TTYPE &T, const RhoType &rhomolar, const VecType &mole_fractions) const
auto get_SIGMAIJ_matrix() const
const auto & get_terms() const
auto R(const VecType &molefrac) const
auto get_lambda_r() const
auto chain_factory(const std::vector< SAFTVRMieCoeffs > &coeffs, const std::optional< Eigen::ArrayXXd > &kmat)
auto get_lambda_a() const
SAFTVRMieNonpolarMixture & operator=(const SAFTVRMieNonpolarMixture &)=delete
SAFTVRMieNonpolarMixture(const std::vector< std::string > &names, const std::optional< Eigen::ArrayXXd > &kmat=std::nullopt, const std::optional< nlohmann::json > &flags=std::nullopt)
auto get_EPSKIJ_matrix() const
auto get_epsilon_over_k_K() const
static auto build_chain(const std::vector< SAFTVRMieCoeffs > &coeffs, const std::optional< Eigen::ArrayXXd > &kmat, const std::optional< nlohmann::json > &flags=std::nullopt)
auto get_core_calcs(double T, double rhomolar, const Eigen::ArrayXd &mole_fractions) const
auto get_sigma_Angstrom() const
auto SAFTVRMieNonpolarfactory(const nlohmann::json &spec)
auto SAFTVRMiefactory(const nlohmann::json &spec)
NLOHMANN_JSON_SERIALIZE_ENUM(EpsilonijFlags, { {EpsilonijFlags::kInvalid, nullptr}, {EpsilonijFlags::kLorentzBerthelot, "Lorentz-Berthelot"}, {EpsilonijFlags::kLafitte, "Lafitte"}, }) class SAFTVRMieLibrary
Manager class for SAFT-VR-Mie coefficients.
auto getbaseval(const T &expr)
T powi(const T &x, int n)
From Ulrich Deiters.
auto quad(const std::function< T(Double)> &F, const Double &a, const Double &b)
auto pow(const double &x, const double &e)
auto get_R_gas()
< Gas constant, according to CODATA 2019, in the given number type
Things that only depend on the components themselves, but not on composition, temperature,...
const std::vector< Eigen::ArrayXXd > fkij
auto get_rhoda1Shatijdrho(const ZetaType &zeta_x, const ZetaType &zeta_x_eff, const ZetaType &dzetaxeffdzetax, double lambda_ij) const
const Eigen::ArrayXXd sigma_ij
const Eigen::ArrayXd epsilon_over_k
const Eigen::ArrayXXd kmat
auto get_uii_over_kB(std::size_t i, const RType &r) const
Eq. A2 from Lafitte.
auto get_a_HS(const RhoType &rhos, const Eigen::Array< ZetaType, 4, 1 > &zeta, const Eigen::Array< DType, 4, 1 > &D) const
Eq. A6 from Lafitte, accounting for the case of rho_s=0, for which the limit is zero.
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, const std::optional< nlohmann::json > &flags=std::nullopt)
const EpsilonijFlags epsilon_ij_flag
const Eigen::ArrayXd lambda_a
const Eigen::ArrayXXd alpha_ij
const Eigen::ArrayXd lambda_r
const Eigen::ArrayXXd C_ij
auto get_core_calcs(const TType &T, const RhoType &rhomolar, const VecType &molefracs) const
auto get_EPSKIJ_K_matrix() const
Get the matrix of with the entries in K.
const Eigen::ArrayXXd epsilon_ij
auto get_Bhatij(const ZetaType &zeta_x, const ZetaType &one_minus_zeta_x3, const IJ &I, const IJ &J) const
const Eigen::ArrayXd sigma_A
auto get_SIGMAIJ_m_matrix() const
Get the matrix of with the entries in m.
auto get_j_cutoff_dii(std::size_t i, const TType &T) const
Solve for the value of for which the integrand in becomes equal to 1 to numerical precision.
const std::vector< Eigen::ArrayXXd > crnij
const std::vector< Eigen::ArrayXXd > carnij
const Eigen::ArrayXXd lambda_r_ij
auto get_KHS(const RhoType &pf) const
Eq. A21 from Lafitte.
const Eigen::ArrayXXd lambda_a_ij
const std::vector< Eigen::ArrayXXd > c2anij
const std::vector< Eigen::ArrayXXd > canij
TType get_dii(std::size_t i, const TType &T) const
const std::vector< Eigen::ArrayXXd > c2rnij
auto get_rhos_dK_HS_drhos(const RhoType &zeta_x) const
auto get_dmat(const TType &T) const
auto get_a1Shatij(const ZetaType &zeta_x_eff, double lambda_ij) const
auto get_rhodBijdrho(const ZetaType &zeta_x, const ZetaType &, const IJ &I, const IJ &J, const ZetaType &Bhatij) const
Coefficients for one fluid.
double lambda_r
The repulsive exponent (the 12 in LJ 12-6 potential)
std::string BibTeXKey
The BibTeXKey for the reference for these coefficients.
double nQ
number of quadrupolar segments
double nmu
number of dipolar segments
double epsilon_over_k
[K] depth of pair potential divided by Boltzman constant
std::string name
Name of fluid.
double mustar2
nondimensional, the reduced dipole moment squared
double sigma_m
[m] segment diameter
double m
number of segments
double lambda_a
The attractive exponent (the 6 in LJ 12-6 potential)
double Qstar2
nondimensional, the reduced quadrupole squared