teqp 0.19.1
Loading...
Searching...
No Matches
saftvrmie.hpp
Go to the documentation of this file.
1/***
2
3 \brief This file contains the contributions that can be composed together to form SAFT models
4
5*/
6
7#pragma once
8
9#include "nlohmann/json.hpp"
10#include "teqp/types.hpp"
11#include "teqp/json_tools.hpp"
12#include "teqp/exceptions.hpp"
13#include "teqp/constants.hpp"
16#include <optional>
17#include <variant>
18
19namespace teqp {
20namespace SAFTVRMie {
21
24 std::string name;
25 double m = -1,
26 sigma_m = -1,
28 lambda_a = -1,
29 lambda_r = -1,
30 mustar2 = 0,
31 nmu = 0,
32 Qstar2 = 0,
33 nQ = 0;
34 std::string BibTeXKey;
35};
36
38
39// map EpsilonijFlags values to JSON as strings
41 {EpsilonijFlags::kInvalid, nullptr},
42 {EpsilonijFlags::kLorentzBerthelot, "Lorentz-Berthelot"},
43 {EpsilonijFlags::kLafitte, "Lafitte"},
44})
45
47class SAFTVRMieLibrary {
48 std::map<std::string, SAFTVRMieCoeffs> coeffs;
49public:
50 SAFTVRMieLibrary() {
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");
54 }
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;
57 coeff.name = name;
58 coeff.m = m;
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));
65 }
66 const auto& get_normal_fluid(const std::string& name) {
67 auto it = coeffs.find(name);
68 if (it != coeffs.end()) {
69 return it->second;
70 }
71 else {
72 throw std::invalid_argument("Bad name:" + name);
73 }
74 }
75 auto get_coeffs(const std::vector<std::string>& names){
76 std::vector<SAFTVRMieCoeffs> c;
77 for (auto n : names){
78 c.push_back(get_normal_fluid(n));
79 }
80 return c;
81 }
82};
83
86 private:
87
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()};
97
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()};
104
105 // Eq. A48
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));
111 mat(j,i) = mat(i,j);
112 }
113 }
114 return mat;
115 }
116
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)));
123 C(j,i) = C(i,j); // symmetric
124 }
125 }
126 return C;
127 }
128
129 // Eq. A26
130 auto get_fkij() const{
131 std::vector<Eigen::ArrayXXd> f_(8); // 0-th element is present, but not initialized
132 for (auto k = 1; k < 8; ++k){
133 f_[k].resize(N,N);
134 };
135 for (auto k = 1; k < 8; ++k){
136 auto phik = phi.col(k-1); // phi matrix is indexed to start at 1, but our matrix starts at 0
137 Eigen::ArrayXXd num(N,N), den(N,N); num.setZero(), den.setZero();
138 for (auto n = 0; n < 4; ++n){
139 num += phik[n]*pow(alpha_ij, n);
140 }
141 for (auto n = 4; n < 7; ++n){
142 den += phik[n]*pow(alpha_ij, n-3);
143 }
144 f_[k] = num/(1 + den);
145 }
146 return f_;
147 }
148
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){
154 sigma(i,j) = (sigma_A(i) + sigma_A(j))/2.0;
155 sigma(j,i) = sigma(i,j); // symmetric
156 }
157 }
158 return sigma;
159 }
160
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){
170 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);
171 eps_(j,i) = eps_(i,j); // symmetric
172 }
173 }
174 return eps_;
175 }
177 Eigen::ArrayXXd eps_(N,N);
178 for (auto i = 0U; i < N; ++i){
179 for (auto j = i; j < N; ++j){
180 eps_(i,j) = (1.0-kmat(i,j))*sqrt(epsilon_over_k(i)*epsilon_over_k(j));
181 eps_(j,i) = eps_(i,j); // symmetric
182 }
183 }
184 return eps_;
185 }
186 else{
187 throw std::invalid_argument("epsilon_ij_flag is invalid");
188 }
189 }
190 auto get_N(){
191 auto sizes = (Eigen::ArrayX<Eigen::Index>(5) << m.size(), epsilon_over_k.size(), sigma_A.size(), lambda_a.size(), lambda_r.size()).finished();
192 if (sizes.maxCoeff() != sizes.minCoeff()){
193 throw teqp::InvalidArgument("sizes of pure component arrays are not all the same");
194 }
195 return sizes[0];
196 }
197
199 auto get_cij(const Eigen::ArrayXXd& lambdaij) const{
200 std::vector<Eigen::ArrayXXd> cij(4);
201 for (auto n = 0; n < 4; ++n){
202 cij[n].resize(N,N);
203 };
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);
213 }
214 }
215 return cij;
216 }
217
219 auto get_canij() const{
220 return get_cij(lambda_a_ij);
221 }
223 auto get_c2anij() const{
224 return get_cij(2.0*lambda_a_ij);
225 }
227 auto get_crnij() const{
228 return get_cij(lambda_r_ij);
229 }
231 auto get_c2rnij() const{
232 return get_cij(2.0*lambda_r_ij);
233 }
235 auto get_carnij() const{
236 return get_cij(lambda_r_ij + lambda_a_ij);
237 }
238
239 EpsilonijFlags get_epsilon_ij(const std::optional<nlohmann::json>& flags){
240 if (flags){
241 const nlohmann::json& j = flags.value();
242 if (j.contains("epsilon_ij")){
243 return j.at("epsilon_ij").get<EpsilonijFlags>();
244 }
245 }
247 }
248
249 public:
250
251 // One entry per component
252 const Eigen::ArrayXd m, epsilon_over_k, sigma_A, lambda_a, lambda_r;
253 const Eigen::ArrayXXd kmat;
254
255 const Eigen::Index N;
257
258 // Calculated matrices for the ij pair
259 const Eigen::ArrayXXd lambda_r_ij, lambda_a_ij, C_ij, alpha_ij, sigma_ij, epsilon_ij; // Matrices of parameters
260
261 const std::vector<Eigen::ArrayXXd> crnij, canij, c2rnij, c2anij, carnij;
262 const std::vector<Eigen::ArrayXXd> fkij; // Matrices of parameters
263
265 const Eigen::ArrayXd& m,
266 const Eigen::ArrayXd& epsilon_over_k,
267 const Eigen::ArrayXd& sigma_m,
268 const Eigen::ArrayXd& lambda_r,
269 const Eigen::ArrayXd& lambda_a,
270 const Eigen::ArrayXXd& kmat,
271 const std::optional<nlohmann::json> & flags = std::nullopt)
273 N(get_N()),
274 epsilon_ij_flag(get_epsilon_ij(flags)),
275 lambda_r_ij(get_lambda_k_ij(lambda_r)), lambda_a_ij(get_lambda_k_ij(lambda_a)),
276 C_ij(get_C_ij()), alpha_ij(C_ij*(1/(lambda_a_ij-3) - 1/(lambda_r_ij-3))),
277 sigma_ij(get_sigma_ij()), epsilon_ij(get_epsilon_ij()),
278 crnij(get_crnij()), canij(get_canij()),
279 c2rnij(get_c2rnij()), c2anij(get_c2anij()), carnij(get_carnij()),
280 fkij(get_fkij())
281 {}
282
284 auto get_EPSKIJ_K_matrix() const { return epsilon_ij; }
286 auto get_SIGMAIJ_m_matrix() const { return sigma_ij/1e10; }
287
289 template<typename RType>
290 auto get_uii_over_kB(std::size_t i, const RType& r) const {
291 auto rstarinv = forceeval(sigma_A[i]/r);
292 return forceeval(C_ij(i,i)*epsilon_over_k[i]*(pow(rstarinv, lambda_r[i]) - pow(rstarinv, lambda_a[i])));
293 }
294
296 template <typename TType>
297 auto get_j_cutoff_dii(std::size_t i, const TType &T) const {
298 auto lambda_a_ = lambda_a(i), lambda_r_ = lambda_r(i);
299 auto EPS = std::numeric_limits<decltype(getbaseval(T))>::epsilon();
300 auto K = forceeval(log(-log(EPS)*T/(C_ij(i,i)*epsilon_ij(i,i))));
301 auto j0 = forceeval(exp(K/lambda_r_)); // this was proposed by longemen3000 (Andrés Riedemann)
302 auto kappa = C_ij(i,i)*epsilon_ij(i,i);
303
304 // Function to return residual and its derivatives w.r.t.
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);
310 return std::make_tuple(forceeval(-u/T-log(EPS)), forceeval(-uprime/T), forceeval(-uprime2/T));
311 };
312 TType j = j0;
313 for (auto counter = 0; counter <= 3; ++counter){
314 // Halley's method steps
315 auto [R, Rprime, Rprime2] = fgh(j);
316 auto denominator = 2.0*Rprime*Rprime-R*Rprime2;
317 if (getbaseval(denominator) < EPS){
318 break;
319 }
320 j -= 2.0*R*Rprime/denominator;
321 }
322 double jbase = getbaseval(j);
323 if (jbase < 1.0){
324 throw teqp::IterationFailure("Cannot obtain a value of j");
325 }
326 return j;
327 }
328
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){
364 return forceeval(1.0-exp(-this->get_uii_over_kB(i, r)/T));
365 };
366
367 // Sum of the two integrals, one is constant, the other is from integration
368 auto rcut = forceeval(sigma_A[i]/get_j_cutoff_dii(i, T));
369 auto integral_contribution = quad<10, TType, TType>(integrand, rcut, sigma_A[i]);
370 auto d = forceeval(rcut + integral_contribution);
371
372 if (getbaseval(d) > sigma_A[i]){
373 throw teqp::IterationFailure("Value of d is larger than sigma; this is impossible");
374 }
375 return d;
376 }
377
378 template <typename TType>
379 auto get_dmat(const TType &T) const{
380 Eigen::Array<TType, Eigen::Dynamic, Eigen::Dynamic> d(N,N);
381 // For the pure components, by integration
382 for (auto i = 0U; i < N; ++i){
383 d(i,i) = get_dii(i, T);
384 }
385 // The cross terms, using the linear mixing rule
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;
389 d(j,i) = d(i,j);
390 }
391 }
392 return d;
393 }
394 // Calculate core parameters that depend on temperature, volume, and composition
395 template <typename TType, typename RhoType, typename VecType>
396 auto get_core_calcs(const TType& T, const RhoType& rhomolar, const VecType& molefracs) const{
397
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));
400 }
401
402 using FracType = std::decay_t<decltype(molefracs[0])>;
403 using NumType = std::common_type_t<TType, RhoType, FracType>;
404
405 // Things that are easy to calculate
406 // ....
407
408 auto dmat = get_dmat(T); // Matrix of diameters of pure and cross terms
409 auto rhoN = forceeval(rhomolar*N_A); // Number density, in molecules/m^3
410 auto mbar = forceeval((molefracs*m).sum()); // Mean number of segments, dimensionless
411 auto rhos = forceeval(rhoN*mbar/1e30); // Mean segment number density, in segments/A^3
412 auto xs = forceeval((m*molefracs/mbar).eval()); // Segment fractions
413
414 constexpr double MY_PI = static_cast<double>(EIGEN_PI);
415 auto pi6 = MY_PI/6;
416
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 Eigen::Array<TRHOType, 4, 1> zeta;
419 for (auto l = 0; l < 4; ++l){
420 TRHOType summer = 0.0;
421 for (auto i = 0U; i < N; ++i){
422 summer += xs(i)*powi(dmat(i,i), l);
423 }
424 zeta(l) = forceeval(pi6*rhos*summer);
425 }
426
427 NumType summer_zeta_x = 0.0;
428 TRHOType summer_zeta_x_bar = 0.0;
429 for (auto i = 0U; i < N; ++i){
430 for (auto j = 0U; j < N; ++j){
431 summer_zeta_x += xs(i)*xs(j)*powi(dmat(i,j), 3)*rhos;
432 summer_zeta_x_bar += xs(i)*xs(j)*powi(sigma_ij(i,j), 3);
433 }
434 }
435
436 auto zeta_x = forceeval(pi6*summer_zeta_x); // Eq. A13
437 auto zeta_x_bar = forceeval(pi6*rhos*summer_zeta_x_bar); // Eq. A23
438 auto zeta_x_bar5 = forceeval(POW2(zeta_x_bar)*POW3(zeta_x_bar)); // (zeta_x_bar)^5
439 auto zeta_x_bar8 = forceeval(zeta_x_bar5*POW3(zeta_x_bar)); // (zeta_x_bar)^8
440
441 // Coefficients in the gdHSij term, do not depend on component,
442 // so calculate them here
443 auto X = forceeval(POW3(1.0 - zeta_x)), X3 = X;
444 auto X2 = forceeval(POW2(1.0 - zeta_x));
445 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
446 auto k1 = forceeval((POW4(zeta_x) + 6.0*POW2(zeta_x) - 12.0*zeta_x)/(2.0*X3));
447 auto k2 = forceeval(-3.0*POW2(zeta_x)/(8.0*X2));
448 auto k3 = forceeval((-POW4(zeta_x) + 3.0*POW2(zeta_x) + 3.0*zeta_x)/(6.0*X3));
449
450 // Pre-calculate the cubes of the diameters
451 auto dmat3 = dmat.array().cube().eval();
452
453 NumType a1kB = 0.0;
454 NumType a2kB2 = 0.0;
455 NumType a3kB3 = 0.0;
456 NumType alphar_chain = 0.0;
457
458 NumType K_HS = get_KHS(zeta_x);
459 NumType rho_dK_HS_drho = get_rhos_dK_HS_drhos(zeta_x);
460
461 for (auto i = 0U; i < N; ++i){
462 for (auto j = i; j < N; ++j){
463 NumType x_0_ij = sigma_ij(i,j)/dmat(i, j);
464
465 // -----------------------
466 // Calculations for a_1/kB
467 // -----------------------
468
469 auto I = [&x_0_ij](double lambda_ij){
470 return forceeval(-(pow(x_0_ij, 3-lambda_ij)-1.0)/(lambda_ij-3.0)); // Eq. A14
471 };
472 auto J = [&x_0_ij](double lambda_ij){
473 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
474 };
475 auto Bhatij_a = this->get_Bhatij(zeta_x, X, I(lambda_a_ij(i,j)), J(lambda_a_ij(i,j)));
476 auto Bhatij_2a = this->get_Bhatij(zeta_x, X, I(2*lambda_a_ij(i,j)), J(2*lambda_a_ij(i,j)));
477 auto Bhatij_r = this->get_Bhatij(zeta_x, X, I(lambda_r_ij(i,j)), J(lambda_r_ij(i,j)));
478 auto Bhatij_2r = this->get_Bhatij(zeta_x, X, I(2*lambda_r_ij(i,j)), J(2*lambda_r_ij(i,j)));
479 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)));
480
481 auto one_term = [this, &x_0_ij, &I, &J, &zeta_x, &X](double lambda_ij, const NumType& zeta_x_eff){
482 return forceeval(
483 pow(x_0_ij, lambda_ij)*(
484 this->get_Bhatij(zeta_x, X, I(lambda_ij), J(lambda_ij))
485 + this->get_a1Shatij(zeta_x_eff, lambda_ij)
486 )
487 );
488 };
489 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);
490 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);
491 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);
492 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);
493
494 NumType a1ij = 2.0*MY_PI*rhos*dmat3(i,j)*epsilon_ij(i,j)*C_ij(i,j)*(
495 one_term(lambda_a_ij(i,j), zeta_x_eff_a) - one_term(lambda_r_ij(i,j), zeta_x_eff_r)
496 ); // divided by k_B
497
498 NumType contribution = xs(i)*xs(j)*a1ij;
499 double factor = (i == j) ? 1.0 : 2.0; // Off-diagonal terms contribute twice
500 a1kB += contribution*factor;
501
502 // --------------------------
503 // Calculations for a_2/k_B^2
504 // --------------------------
505
506 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);
507 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);
508 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);
509 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);
510 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);
511 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);
512
513 NumType chi_ij = fkij[1](i,j)*zeta_x_bar + fkij[2](i,j)*zeta_x_bar5 + fkij[3](i,j)*zeta_x_bar8;
514 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))*(
515 one_term(2.0*lambda_a_ij(i,j), zeta_x_eff_2a)
516 -2.0*one_term(lambda_a_ij(i,j)+lambda_r_ij(i,j), zeta_x_eff_ar)
517 +one_term(2.0*lambda_r_ij(i,j), zeta_x_eff_2r)
518 ); // divided by k_B^2
519
520 NumType contributiona2 = xs(i)*xs(j)*a2ij; // Eq. A19
521 a2kB2 += contributiona2*factor;
522
523 // --------------------------
524 // Calculations for a_3/k_B^3
525 // --------------------------
526 auto a3ij = -POW3(epsilon_ij(i,j))*fkij[4](i,j)*zeta_x_bar*exp(
527 fkij[5](i,j)*zeta_x_bar + fkij[6](i,j)*POW2(zeta_x_bar)
528 ); // divided by k_B^3
529 NumType contributiona3 = xs(i)*xs(j)*a3ij; // Eq. A25
530 a3kB3 += contributiona3*factor;
531
532 if (i == j){
533 // ------------------
534 // Chain contribution
535 // ------------------
536
537 // Eq. A29
538 auto gdHSii = exp(k0 + k1*x_0_ij + k2*POW2(x_0_ij) + k3*POW3(x_0_ij));
539
540 // The g1 terms
541 // ....
542
543 // This is the function for the second part (not the partial) that goes in g_{1,ii},
544 // divided by 2*PI*d_ij^3*epsilon*rhos
545 auto g1_term = [&one_term](double lambda_ij, const NumType& zeta_x_eff){
546 return forceeval(lambda_ij*one_term(lambda_ij, zeta_x_eff));
547 };
548 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));
549
550 // Bhat = B*rho*kappa; diff(Bhat, rho) = Bhat + rho*dBhat/drho; kappa = 2*pi*eps*d^3
551 // This is the function for the partial derivative rhos*(da1ij/drhos),
552 // divided by 2*PI*d_ij^3*epsilon*rhos
553 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){
554 auto I_ = I(lambda_ij);
555 auto J_ = J(lambda_ij);
556 auto rhosda1Sdrhos = this->get_rhoda1Shatijdrho(zeta_x, zeta_x_eff, dzetaxeff_dzetax, lambda_ij);
557 auto rhosdBdrhos = this->get_rhodBijdrho(zeta_x, X, I_, J_, Bhatij);
558 return forceeval(pow(x_0_ij, lambda_ij)*(rhosda1Sdrhos + rhosdBdrhos));
559 };
560 // This is rhos*d(a_1ij)/drhos/(2*pi*d^3*eps*rhos)
561 auto da1iidrhos_term = C_ij(i,j)*(
562 rhosda1iidrhos_term(lambda_a_ij(i,i), zeta_x_eff_a, dzeta_x_eff_dzetax_a, Bhatij_a)
563 -rhosda1iidrhos_term(lambda_r_ij(i,i), zeta_x_eff_r, dzeta_x_eff_dzetax_r, Bhatij_r)
564 );
565 auto g1ii = 3.0*da1iidrhos_term + g1_noderivterm;
566
567 // The g2 terms
568 // ....
569
570 // This is the second part (not the partial deriv.) that goes in g_{2,ii},
571 // divided by 2*PI*d_ij^3*epsilon*rhos
572 auto g2_noderivterm = -POW2(C_ij(i,i))*K_HS*(
573 lambda_a_ij(i,j)*one_term(2*lambda_a_ij(i,j), zeta_x_eff_2a)
574 -(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)
575 +lambda_r_ij(i,j)*one_term(2*lambda_r_ij(i,j), zeta_x_eff_2r)
576 );
577 // This is [rhos*d(a_2ij/(1+chi_ij))/drhos]/(2*pi*d^3*eps*rhos)
578 auto da2iidrhos_term = 0.5*POW2(C_ij(i,j))*(
579 rho_dK_HS_drho*(
580 one_term(2.0*lambda_a_ij(i,j), zeta_x_eff_2a)
581 -2.0*one_term(lambda_a_ij(i,j)+lambda_r_ij(i,j), zeta_x_eff_ar)
582 +one_term(2.0*lambda_r_ij(i,j), zeta_x_eff_2r))
583 +K_HS*(
584 rhosda1iidrhos_term(2.0*lambda_a_ij(i,i), zeta_x_eff_2a, dzeta_x_eff_dzetax_2a, Bhatij_2a)
585 -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)
586 +rhosda1iidrhos_term(2.0*lambda_r_ij(i,i), zeta_x_eff_2r, dzeta_x_eff_dzetax_2r, Bhatij_2r)
587 )
588 );
589 auto g2MCAij = 3.0*da2iidrhos_term + g2_noderivterm;
590
591 auto betaepsilon = epsilon_ij(i,i)/T; // (1/(kB*T))/epsilon
592 auto theta = exp(betaepsilon)-1.0;
593 auto phi7 = phi.col(6);
594 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
595 auto g2ii = (1.0+gamma_cij)*g2MCAij;
596
597 NumType giiMie = gdHSii*exp((betaepsilon*g1ii + POW2(betaepsilon)*g2ii)/gdHSii);
598 alphar_chain -= molefracs[i]*(m[i]-1.0)*log(giiMie);
599 }
600 }
601 }
602
603 auto ahs = get_a_HS(rhos, zeta);
604 // Eq. A5 from Lafitte, multiplied by mbar
605 auto alphar_mono = forceeval(mbar*(ahs + a1kB/T + a2kB2/(T*T) + a3kB3/(T*T*T)));
606
607 using dmat_t = decltype(dmat);
608 using rhos_t = decltype(rhos);
609 using rhoN_t = decltype(rhoN);
610 using mbar_t = decltype(mbar);
611 using xs_t = decltype(xs);
612 using zeta_t = decltype(zeta);
613 using zeta_x_t = decltype(zeta_x);
614 using zeta_x_bar_t = decltype(zeta_x_bar);
615 using alphar_mono_t = decltype(alphar_mono);
616 using a1kB_t = decltype(a1kB);
617 using a2kB2_t = decltype(a2kB2);
618 using a3kB3_t = decltype(a3kB3);
619 using alphar_chain_t = decltype(alphar_chain);
620 struct vals{
621 dmat_t dmat;
622 rhos_t rhos;
623 rhoN_t rhoN;
624 mbar_t mbar;
625 xs_t xs;
626 zeta_t zeta;
627 zeta_x_t zeta_x;
628 zeta_x_bar_t zeta_x_bar;
629 alphar_mono_t alphar_mono;
630 a1kB_t a1kB;
631 a2kB2_t a2kB2;
632 a3kB3_t a3kB3;
633 alphar_chain_t alphar_chain;
634 };
635 return vals{dmat, rhos, rhoN, mbar, xs, zeta, zeta_x, zeta_x_bar, alphar_mono, a1kB, a2kB2, a3kB3, alphar_chain};
636 }
637
639 template<typename RhoType>
640 auto get_KHS(const RhoType& pf) const {
641 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));
642 }
643
649 template<typename RhoType>
650 auto get_rhos_dK_HS_drhos(const RhoType& zeta_x) const {
651 auto num = -4.0*POW3(zeta_x - 1.0)*(POW2(zeta_x) - 5.0*zeta_x - 2.0);
652 auto den = POW2(POW4(zeta_x) - 4.0*POW3(zeta_x) + 4.0*POW2(zeta_x) + 4.0*zeta_x + 1.0);
653 return forceeval(num/den*zeta_x);
654 }
655
657 template<typename RhoType, typename ZetaType>
658 auto get_a_HS(const RhoType& rhos, const Eigen::Array<ZetaType, 4, 1>& zeta) const{
659 constexpr double MY_PI = static_cast<double>(EIGEN_PI);
660 if (getbaseval(rhos) == 0){
661 // The way in which the function goes to zero is subtle, and the factor of 4 accounts for the contributions from each term
662 return forceeval(4.0*zeta[3]);
663 }
664 else{
665 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])));
666 }
667 }
668
677 template<typename ZetaType, typename IJ>
678 auto get_Bhatij(const ZetaType& zeta_x, const ZetaType& one_minus_zeta_x3, const IJ& I, const IJ& J) const{
679 return forceeval(
680 (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
681 );
682 }
683
696 template<typename ZetaType, typename IJ>
697 auto get_rhodBijdrho(const ZetaType& zeta_x, const ZetaType& /*one_minus_zeta_x3*/, const IJ& I, const IJ& J, const ZetaType& Bhatij) const{
698 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));
699 return forceeval(Bhatij + dBhatdzetax*zeta_x);
700 }
701
713 template<typename ZetaType>
714 auto get_a1Shatij(const ZetaType& zeta_x_eff, double lambda_ij) const{
715 return forceeval(
716 -1.0/(lambda_ij-3.0)*(1.0-zeta_x_eff/2.0)/POW3(forceeval(1.0-zeta_x_eff))
717 );
718 }
719
731 template<typename ZetaType>
732 auto get_rhoda1Shatijdrho(const ZetaType& zeta_x, const ZetaType& zeta_x_eff, const ZetaType& dzetaxeffdzetax, double lambda_ij) const{
733 auto zetaxda1Shatdzetax = ((2.0*zeta_x_eff - 5.0)*dzetaxeffdzetax)/(2.0*(lambda_ij-3)*POW4(zeta_x_eff-1.0))*zeta_x;
734 return forceeval(get_a1Shatij(zeta_x_eff, lambda_ij) + zetaxda1Shatdzetax);
735 }
736};
737
742private:
743
744 std::vector<std::string> names, bibtex;
746 const std::optional<SAFTpolar::multipolar_contributions_variant> polar; // Can be present or not
747
748 static void check_kmat(const Eigen::ArrayXXd& kmat, Eigen::Index N) {
749 if (kmat.size() == 0){
750 return;
751 }
752 if (kmat.cols() != kmat.rows()) {
753 throw teqp::InvalidArgument("kmat rows and columns are not identical");
754 }
755 if (kmat.cols() != N) {
756 throw teqp::InvalidArgument("kmat needs to be a square matrix the same size as the number of components");
757 }
758 };
759 static auto get_coeffs_from_names(const std::vector<std::string> &names){
760 SAFTVRMieLibrary library;
761 return library.get_coeffs(names);
762 }
763 auto get_names(const std::vector<SAFTVRMieCoeffs> &coeffs){
764 std::vector<std::string> names_;
765 for (auto c : coeffs){
766 names_.push_back(c.name);
767 }
768 return names_;
769 }
770 auto get_bibtex(const std::vector<SAFTVRMieCoeffs> &coeffs){
771 std::vector<std::string> keys_;
772 for (auto c : coeffs){
773 keys_.push_back(c.BibTeXKey);
774 }
775 return keys_;
776 }
777public:
778 static auto build_chain(const std::vector<SAFTVRMieCoeffs> &coeffs, const std::optional<Eigen::ArrayXXd>& kmat, const std::optional<nlohmann::json>& flags = std::nullopt){
779 if (kmat){
780 check_kmat(kmat.value(), coeffs.size());
781 }
782 const std::size_t N = coeffs.size();
783 Eigen::ArrayXd m(N), epsilon_over_k(N), sigma_m(N), lambda_r(N), lambda_a(N);
784 auto i = 0;
785 for (const auto &coeff : coeffs) {
786 m[i] = coeff.m;
787 epsilon_over_k[i] = coeff.epsilon_over_k;
788 sigma_m[i] = coeff.sigma_m;
789 lambda_r[i] = coeff.lambda_r;
790 lambda_a[i] = coeff.lambda_a;
791 i++;
792 }
793 if (kmat){
794 return SAFTVRMieChainContributionTerms(m, epsilon_over_k, sigma_m, lambda_r, lambda_a, std::move(kmat.value()), flags);
795 }
796 else{
797 auto mat = Eigen::ArrayXXd::Zero(N,N);
798 return SAFTVRMieChainContributionTerms(m, epsilon_over_k, sigma_m, lambda_r, lambda_a, std::move(mat), flags);
799 }
800 }
801 auto build_polar(const std::vector<SAFTVRMieCoeffs> &coeffs) -> decltype(this->polar){
802 Eigen::ArrayXd mustar2(coeffs.size()), nmu(coeffs.size()), Qstar2(coeffs.size()), nQ(coeffs.size());
803 auto i = 0;
804 for (const auto &coeff : coeffs) {
805 mustar2[i] = coeff.mustar2;
806 nmu[i] = coeff.nmu;
807 Qstar2[i] = coeff.Qstar2;
808 nQ[i] = coeff.nQ;
809 i++;
810 }
811 bool has_dipolar = ((mustar2*nmu).cwiseAbs().sum() == 0);
812 bool has_quadrupolar = ((Qstar2*nQ).cwiseAbs().sum() == 0);
813 if (!has_dipolar && !has_quadrupolar){
814 return std::nullopt; // No dipolar or quadrupolar contribution is present
815 }
816 else{
817 // The dispersive and hard chain initialization has already happened at this point
818 return SAFTpolar::MultipolarContributionGrossVrabec(terms.m, terms.sigma_A, terms.epsilon_over_k, mustar2, nmu, Qstar2, nQ);
819 }
820 }
821
822public:
823 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){};
824 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)) {};
825 SAFTVRMieMixture(SAFTVRMieChainContributionTerms&& terms, const std::vector<SAFTVRMieCoeffs> &coeffs, std::optional<SAFTpolar::multipolar_contributions_variant> &&polar = std::nullopt) : names(get_names(coeffs)), bibtex(get_bibtex(coeffs)), terms(std::move(terms)), polar(std::move(polar)) {};
826
827
828// PCSAFTMixture( const PCSAFTMixture& ) = delete; // non construction-copyable
829 SAFTVRMieMixture& operator=( const SAFTVRMieMixture& ) = delete; // non copyable
830
831 auto chain_factory(const std::vector<SAFTVRMieCoeffs> &coeffs, const std::optional<Eigen::ArrayXXd>& kmat){
832 SAFTVRMieMixture::build_chain(coeffs, kmat);
833 }
834
835 const auto& get_polar() const { return polar; }
836
837 // Checker for whether a polar term is present
838 bool has_polar() const{ return polar.has_value(); }
839
840 const auto& get_terms() const { return terms; }
841 auto get_core_calcs(double T, double rhomolar, const Eigen::ArrayXd& mole_fractions) const {
842 auto val = terms.get_core_calcs(T, rhomolar, mole_fractions);
843
844 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;};
845 auto fromArrayXX = [](const Eigen::ArrayXXd &x){
846 std::size_t N = x.rows();
847 std::vector<std::vector<double>> n; n.resize(N);
848 for (auto i = 0U; i < N; ++i){
849 n[i].resize(N);
850 for (auto j = 0U; j < N; ++j){
851 n[i][j] = x(i,j);
852 }
853 }
854 return n;
855 };
856 return nlohmann::json{
857 {"dmat", fromArrayXX(val.dmat)},
858 {"rhos", val.rhos},
859 {"rhoN", val.rhoN},
860 {"mbar", val.mbar},
861 {"xs", fromArrayX(val.xs)},
862 {"zeta", fromArrayX(val.zeta)},
863 {"zeta_x", val.zeta_x},
864 {"zeta_x_bar", val.zeta_x_bar},
865 {"alphar_mono", val.alphar_mono},
866 {"a1kB", val.a1kB},
867 {"a2kB2", val.a2kB2},
868 {"a3kB3", val.a3kB3},
869 {"alphar_chain", val.alphar_chain}
870 };
871 }
872 auto get_names() const { return names; }
873 auto get_BibTeXKeys() const { return bibtex; }
874 auto get_m() const { return terms.m; }
875 auto get_sigma_Angstrom() const { return (terms.sigma_A).eval(); }
876 auto get_sigma_m() const { return terms.sigma_A/1e10; }
877 auto get_epsilon_over_k_K() const { return terms.epsilon_over_k; }
878 auto get_kmat() const { return terms.kmat; }
879 auto get_lambda_r() const { return terms.lambda_r; }
880 auto get_lambda_a() const { return terms.lambda_a; }
881 auto get_EPSKIJ_matrix() const { return terms.get_EPSKIJ_K_matrix(); }
882 auto get_SIGMAIJ_matrix() const { return terms.get_SIGMAIJ_m_matrix(); }
883
884 // template<typename VecType>
885 // double max_rhoN(const double T, const VecType& mole_fractions) const {
886 // auto N = mole_fractions.size();
887 // Eigen::ArrayX<double> d(N);
888 // for (auto i = 0; i < N; ++i) {
889 // d[i] = sigma_Angstrom[i] * (1.0 - 0.12 * exp(-3.0 * epsilon_over_k[i] / T));
890 // }
891 // return 6 * 0.74 / EIGEN_PI / (mole_fractions*m*powvec(d, 3)).sum()*1e30; // particles/m^3
892 // }
893
894 template<class VecType>
895 auto R(const VecType& molefrac) const {
896 return get_R_gas<decltype(molefrac[0])>();
897 }
898
899 template<typename TTYPE, typename RhoType, typename VecType>
900 auto alphar(const TTYPE& T, const RhoType& rhomolar, const VecType& mole_fractions) const {
901 // First values for the Mie chain with dispersion (always included)
902 error_if_expr(T); error_if_expr(rhomolar);
903 auto vals = terms.get_core_calcs(T, rhomolar, mole_fractions);
904 using type = std::common_type_t<TTYPE, RhoType, decltype(mole_fractions[0])>;
905 type alphar = vals.alphar_mono + vals.alphar_chain;
906 type packing_fraction = vals.zeta[3];
907
908 if (polar){ // polar term is present
910 auto visitor = [&T, &rhomolar, &mole_fractions, &packing_fraction](const auto& contrib) -> type {
911
912 constexpr mas arg_spec = std::decay_t<decltype(contrib)>::arg_spec;
913 if constexpr(arg_spec == mas::TK_rhoNA3_packingfraction_molefractions){
914 RhoType rho_A3 = rhomolar*N_A*1e-30;
915 type alpha = contrib.eval(T, rho_A3, packing_fraction, mole_fractions).alpha;
916 return alpha;
917 }
918 else if constexpr(arg_spec == mas::TK_rhoNm3_rhostar_molefractions){
919 RhoType rhoN_m3 = rhomolar*N_A;
920 auto rhostar = contrib.get_rhostar(rhoN_m3, packing_fraction, mole_fractions);
921 return contrib.eval(T, rhoN_m3, rhostar, mole_fractions).alpha;
922 }
923 else{
924 throw teqp::InvalidArgument("Don't know how to handle this kind of arguments in polar term");
925 }
926 };
927 alphar += std::visit(visitor, polar.value());
928 }
929
930 return forceeval(alphar);
931 }
932};
933
934inline auto SAFTVRMiefactory(const nlohmann::json & spec){
935
936 std::optional<Eigen::ArrayXXd> kmat;
937 if (spec.contains("kmat") && spec.at("kmat").is_array() && spec.at("kmat").size() > 0){
938 kmat = build_square_matrix(spec["kmat"]);
939 }
940
941 if (spec.contains("names")){
942 std::vector<std::string> names = spec["names"];
943 if (kmat && static_cast<std::size_t>(kmat.value().rows()) != names.size()){
944 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()));
945 }
946 return SAFTVRMieMixture(names, kmat);
947 }
948 else if (spec.contains("coeffs")){
949 bool something_polar = false;
950 std::vector<SAFTVRMieCoeffs> coeffs;
951 for (auto j : spec["coeffs"]) {
953 c.name = j.at("name");
954 c.m = j.at("m");
955 c.sigma_m = (j.contains("sigma_m")) ? j.at("sigma_m").get<double>() : j.at("sigma_Angstrom").get<double>()/1e10;
956 c.epsilon_over_k = j.at("epsilon_over_k");
957 c.lambda_r = j.at("lambda_r");
958 c.lambda_a = j.at("lambda_a");
959 c.BibTeXKey = j.at("BibTeXKey");
960
961 // These are legacy definitions of the polar moments
962 if (j.contains("(mu^*)^2") && j.contains("nmu")){
963 c.mustar2 = j.at("(mu^*)^2");
964 c.nmu = j.at("nmu");
965 something_polar = true;
966 }
967 if (j.contains("(Q^*)^2") && j.contains("nQ")){
968 c.Qstar2 = j.at("(Q^*)^2");
969 c.nQ = j.at("nQ");
970 something_polar = true;
971 }
972 if (j.contains("Q_Cm2") || j.contains("Q_DA") || j.contains("mu_Cm") || j.contains("mu_D")){
973 something_polar = true;
974 }
975 coeffs.push_back(c);
976 }
977 if (kmat && static_cast<std::size_t>(kmat.value().rows()) != coeffs.size()){
978 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()));
979 }
980
981 if (!something_polar){
982 // Nonpolar, just m, epsilon, sigma and possibly a kmat matrix with kij coefficients
983 return SAFTVRMieMixture(SAFTVRMieMixture::build_chain(coeffs, kmat), coeffs);
984 }
985 else{
986 // Polar term is also provided, along with the chain terms
987 std::string polar_model = "GrossVrabec"; // This is the default, as it was the first one implemented
988 if (spec.contains("polar_model")){
989 polar_model = spec["polar_model"];
990 }
991 std::optional<nlohmann::json> SAFTVRMie_flags = std::nullopt;
992 if (spec.contains("SAFTVRMie_flags")){
993 SAFTVRMie_flags = spec["SAFTVRMie_flags"];
994 }
995 std::optional<nlohmann::json> polar_flags = std::nullopt;
996 if (spec.contains("polar_flags")){
997 polar_flags = spec["polar_flags"];
998 }
999 else{
1000 // Set to the default value
1001 polar_flags = nlohmann::json{{"approach", "use_packing_fraction"}};
1002 }
1003
1004 // Go back and extract the dipolar and quadrupolar terms from
1005 // the JSON, in base SI units
1006 const double D_to_Cm = 3.33564e-30; // C m/D
1007 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)
1008 const double Qstar2factor = 1.0/(4*static_cast<double>(EIGEN_PI)*8.8541878128e-12*1.380649e-23); // same as mustar2factor
1009 auto N = coeffs.size();
1010 Eigen::ArrayXd ms(N), epsks(N), sigma_ms(N), mu_Cm(N), Q_Cm2(N), nQ(N), nmu(N);
1011 Eigen::Index i = 0;
1012 for (auto j : spec["coeffs"]) {
1013 double m = j.at("m");
1014 double sigma_m = (j.contains("sigma_m")) ? j.at("sigma_m").get<double>() : j.at("sigma_Angstrom").get<double>()/1e10;
1015 double epsilon_over_k = j.at("epsilon_over_k");
1016 auto get_dipole_Cm = [&]() -> double {
1017 if (j.contains("(mu^*)^2") && j.contains("nmu")){
1018 // Terms defined like in Gross&Vrabec; backwards-compatibility
1019 double mustar2 = j.at("(mu^*)^2");
1020 return sqrt(mustar2*(m*epsilon_over_k*pow(sigma_m, 3))/mustar2factor);
1021 }
1022 else if (j.contains("mu_Cm")){
1023 return j.at("mu_Cm");
1024 }
1025 else if (j.contains("mu_D")){
1026 return j.at("mu_D").get<double>()*D_to_Cm;
1027 }
1028 else{
1029 return 0.0;
1030 }
1031 };
1032 auto get_quadrupole_Cm2 = [&]() -> double{
1033 if (j.contains("(Q^*)^2") && j.contains("nQ")){
1034 // Terms defined like in Gross&Vrabec; backwards-compatibility
1035 double Qstar2 = j.at("(Q^*)^2");
1036 return sqrt(Qstar2*(m*epsilon_over_k*pow(sigma_m, 5))/Qstar2factor);
1037 }
1038 else if (j.contains("Q_Cm2")){
1039 return j.at("Q_Cm2");
1040 }
1041 else if (j.contains("Q_DA")){
1042 return j.at("Q_DA").get<double>()*D_to_Cm/1e10;
1043 }
1044 else{
1045 return 0.0;
1046 }
1047 };
1048 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();
1049 nmu(i) = (j.contains("nmu") ? j["nmu"].get<double>() : 0.0);
1050 nQ(i) = (j.contains("nQ") ? j["nQ"].get<double>() : 0.0);
1051 i++;
1052 };
1053 nlohmann::json SAFTVRMieFlags;
1054
1055 auto chain = SAFTVRMieMixture::build_chain(coeffs, kmat, SAFTVRMie_flags);
1056 auto EPSKIJ = chain.get_EPSKIJ_K_matrix(); // In units of K
1057 auto SIGMAIJ = chain.get_SIGMAIJ_m_matrix(); // In units of m
1058
1059 using namespace SAFTpolar;
1060 if (polar_model == "GrossVrabec"){
1061 auto mustar2 = (mustar2factor*mu_Cm.pow(2)/(ms*epsks*sigma_ms.pow(3))).eval();
1062 auto Qstar2 = (Qstar2factor*Q_Cm2.pow(2)/(ms*epsks*sigma_ms.pow(5))).eval();
1063 auto polar = MultipolarContributionGrossVrabec(ms, sigma_ms*1e10, epsks, mustar2, nmu, Qstar2, nQ);
1064 return SAFTVRMieMixture(std::move(chain), coeffs, std::move(polar));
1065 }
1066 if (polar_model == "GubbinsTwu+Luckas"){
1067 using MCGTL = MultipolarContributionGubbinsTwu<LuckasJIntegral, LuckasKIntegral>;
1068 auto mubar2 = (mustar2factor*mu_Cm.pow(2)/(epsks*sigma_ms.pow(3))).eval();
1069 auto Qbar2 = (Qstar2factor*Q_Cm2.pow(2)/(epsks*sigma_ms.pow(5))).eval();
1070 auto polar = MCGTL(sigma_ms, epsks, mubar2, Qbar2, multipolar_rhostar_approach::use_packing_fraction);
1071 return SAFTVRMieMixture(std::move(chain), coeffs, std::move(polar));
1072 }
1073 if (polar_model == "GubbinsTwu+GubbinsTwu"){
1074 using MCGG = MultipolarContributionGubbinsTwu<GubbinsTwuJIntegral, GubbinsTwuKIntegral>;
1075 auto mubar2 = (mustar2factor*mu_Cm.pow(2)/(epsks*sigma_ms.pow(3))).eval();
1076 auto Qbar2 = (Qstar2factor*Q_Cm2.pow(2)/(epsks*sigma_ms.pow(5))).eval();
1077 auto polar = MCGG(sigma_ms, epsks, mubar2, Qbar2, multipolar_rhostar_approach::use_packing_fraction);
1078 return SAFTVRMieMixture(std::move(chain), coeffs, std::move(polar));
1079 }
1080 if (polar_model == "GubbinsTwu+Gottschalk"){
1081 using MCGG = MultipolarContributionGubbinsTwu<GottschalkJIntegral, GottschalkKIntegral>;
1082 auto mubar2 = (mustar2factor*mu_Cm.pow(2)/(epsks*sigma_ms.pow(3))).eval();
1083 auto Qbar2 = (Qstar2factor*Q_Cm2.pow(2)/(epsks*sigma_ms.pow(5))).eval();
1084 auto polar = MCGG(sigma_ms, epsks, mubar2, Qbar2, multipolar_rhostar_approach::use_packing_fraction);
1085 return SAFTVRMieMixture(std::move(chain), coeffs, std::move(polar));
1086 }
1087
1088 if (polar_model == "GrayGubbins+GubbinsTwu"){
1089 using MCGG = MultipolarContributionGrayGubbins<GubbinsTwuJIntegral, GubbinsTwuKIntegral>;
1090 auto polar = MCGG(sigma_ms, epsks, SIGMAIJ, EPSKIJ, mu_Cm, Q_Cm2, polar_flags);
1091 return SAFTVRMieMixture(std::move(chain), coeffs, std::move(polar));
1092 }
1093// if (polar_model == "GrayGubbins+Gottschalk"){
1094// using MCGG = MultipolarContributionGrayGubbins<GottschalkJIntegral, GottschalkKIntegral>;
1095// auto polar = MCGG(sigma_ms, epsks, mu_Cm, Q_Cm2, polar_flags);
1096// return SAFTVRMieMixture(std::move(chain), std::move(polar));
1097// }
1098 if (polar_model == "GrayGubbins+Luckas"){
1099 using MCGG = MultipolarContributionGrayGubbins<LuckasJIntegral, LuckasKIntegral>;
1100 auto polar = MCGG(sigma_ms, epsks, SIGMAIJ, EPSKIJ, mu_Cm, Q_Cm2, polar_flags);
1101 return SAFTVRMieMixture(std::move(chain), coeffs, std::move(polar));
1102 }
1103
1104
1105 if (polar_model == "GubbinsTwu+Luckas+GubbinsTwuRhostar"){
1106 using MCGTL = MultipolarContributionGubbinsTwu<LuckasJIntegral, LuckasKIntegral>;
1107 auto mubar2 = (mustar2factor*mu_Cm.pow(2)/(epsks*sigma_ms.pow(3))).eval();
1108 auto Qbar2 = (Qstar2factor*Q_Cm2.pow(2)/(epsks*sigma_ms.pow(5))).eval();
1109 auto polar = MCGTL(sigma_ms, epsks, mubar2, Qbar2, multipolar_rhostar_approach::calculate_Gubbins_rhostar);
1110 return SAFTVRMieMixture(std::move(chain), coeffs, std::move(polar));
1111 }
1112 if (polar_model == "GubbinsTwu+GubbinsTwu+GubbinsTwuRhostar"){
1113 using MCGG = MultipolarContributionGubbinsTwu<GubbinsTwuJIntegral, GubbinsTwuKIntegral>;
1114 auto mubar2 = (mustar2factor*mu_Cm.pow(2)/(epsks*sigma_ms.pow(3))).eval();
1115 auto Qbar2 = (Qstar2factor*Q_Cm2.pow(2)/(epsks*sigma_ms.pow(5))).eval();
1116 auto polar = MCGG(sigma_ms, epsks, mubar2, Qbar2, multipolar_rhostar_approach::calculate_Gubbins_rhostar);
1117 return SAFTVRMieMixture(std::move(chain), coeffs, std::move(polar));
1118 }
1119 throw teqp::InvalidArgument("didn't understand this polar_model:"+polar_model);
1120 }
1121 }
1122 else{
1123 throw std::invalid_argument("you must provide names or coeffs, but not both");
1124 }
1125}
1126
1127} /* namespace SAFTVRMie */
1128}; // namespace teqp
A class used to evaluate mixtures using the SAFT-VR-Mie model.
SAFTVRMieMixture & operator=(const SAFTVRMieMixture &)=delete
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 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
static auto build_chain(const std::vector< SAFTVRMieCoeffs > &coeffs, const std::optional< Eigen::ArrayXXd > &kmat, const std::optional< nlohmann::json > &flags=std::nullopt)
#define X(f)
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.
Definition saftvrmie.hpp:40
auto POW2(const A &x)
auto build_square_matrix
auto POW3(const A &x)
auto getbaseval(const T &expr)
Definition types.hpp:84
void error_if_expr(T &&)
Definition types.hpp:63
T powi(const T &x, int n)
From Ulrich Deiters.
Definition types.hpp:133
auto pow(const double &x, const double &e)
Definition types.hpp:189
auto forceeval(T &&expr)
Definition types.hpp:46
auto POW4(const A &x)
Things that only depend on the components themselves, but not on composition, temperature,...
Definition saftvrmie.hpp:85
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
auto get_uii_over_kB(std::size_t i, const RType &r) const
Eq. A2 from Lafitte.
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)
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.
auto get_Bhatij(const ZetaType &zeta_x, const ZetaType &one_minus_zeta_x3, const IJ &I, const IJ &J) const
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
auto get_a_HS(const RhoType &rhos, const Eigen::Array< ZetaType, 4, 1 > &zeta) const
Eq. A6 from Lafitte, accounting for the case of rho_s=0, for which the limit is zero.
auto get_KHS(const RhoType &pf) const
Eq. A21 from Lafitte.
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_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.
Definition saftvrmie.hpp:23
double lambda_r
The repulsive exponent (the 12 in LJ 12-6 potential)
Definition saftvrmie.hpp:29
std::string BibTeXKey
The BibTeXKey for the reference for these coefficients.
Definition saftvrmie.hpp:34
double nQ
number of quadrupolar segments
Definition saftvrmie.hpp:33
double nmu
number of dipolar segments
Definition saftvrmie.hpp:31
double epsilon_over_k
[K] depth of pair potential divided by Boltzman constant
Definition saftvrmie.hpp:27
std::string name
Name of fluid.
Definition saftvrmie.hpp:24
double mustar2
nondimensional, the reduced dipole moment squared
Definition saftvrmie.hpp:30
double sigma_m
[m] segment diameter
Definition saftvrmie.hpp:26
double m
number of segments
Definition saftvrmie.hpp:25
double lambda_a
The attractive exponent (the 6 in LJ 12-6 potential)
Definition saftvrmie.hpp:28
double Qstar2
nondimensional, the reduced quadrupole squared
Definition saftvrmie.hpp:32