teqp 0.22.0
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 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){
422 DType summer = 0.0;
423 for (auto i = 0U; i < N; ++i){
424 summer += xs(i)*powi(dmat(i,i), l);
425 }
426 D(l) = forceeval(pi6*summer);
427 zeta(l) = forceeval(D(l)*rhos);
428 }
429
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);
436 }
437 }
438
439 auto zeta_x = forceeval(pi6*summer_zeta_x); // Eq. A13
440 auto zeta_x_bar = forceeval(pi6*rhos*summer_zeta_x_bar); // Eq. A23
441 auto zeta_x_bar5 = forceeval(POW2(zeta_x_bar)*POW3(zeta_x_bar)); // (zeta_x_bar)^5
442 auto zeta_x_bar8 = forceeval(zeta_x_bar5*POW3(zeta_x_bar)); // (zeta_x_bar)^8
443
444 // Coefficients in the gdHSij term, do not depend on component,
445 // so calculate them here
446 auto X = forceeval(POW3(1.0 - zeta_x)), X3 = X;
447 auto X2 = forceeval(POW2(1.0 - zeta_x));
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)); // Eq. A30
449 auto k1 = forceeval((POW4(zeta_x) + 6.0*POW2(zeta_x) - 12.0*zeta_x)/(2.0*X3));
450 auto k2 = forceeval(-3.0*POW2(zeta_x)/(8.0*X2));
451 auto k3 = forceeval((-POW4(zeta_x) + 3.0*POW2(zeta_x) + 3.0*zeta_x)/(6.0*X3));
452
453 // Pre-calculate the cubes of the diameters
454 auto dmat3 = dmat.array().cube().eval();
455
456 NumType a1kB = 0.0;
457 NumType a2kB2 = 0.0;
458 NumType a3kB3 = 0.0;
459 NumType alphar_chain = 0.0;
460
461 NumType K_HS = get_KHS(zeta_x);
462 NumType rho_dK_HS_drho = get_rhos_dK_HS_drhos(zeta_x);
463
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);
467
468 // -----------------------
469 // Calculations for a_1/kB
470 // -----------------------
471
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)); // Eq. A14
474 };
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))); // Eq. A15
477 };
478 auto Bhatij_a = this->get_Bhatij(zeta_x, X, I(lambda_a_ij(i,j)), J(lambda_a_ij(i,j)));
479 auto Bhatij_2a = this->get_Bhatij(zeta_x, X, I(2*lambda_a_ij(i,j)), J(2*lambda_a_ij(i,j)));
480 auto Bhatij_r = this->get_Bhatij(zeta_x, X, I(lambda_r_ij(i,j)), J(lambda_r_ij(i,j)));
481 auto Bhatij_2r = this->get_Bhatij(zeta_x, X, I(2*lambda_r_ij(i,j)), J(2*lambda_r_ij(i,j)));
482 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)));
483
484 auto one_term = [this, &x_0_ij, &I, &J, &zeta_x, &X](double lambda_ij, const NumType& zeta_x_eff){
485 return forceeval(
486 pow(x_0_ij, lambda_ij)*(
487 this->get_Bhatij(zeta_x, X, I(lambda_ij), J(lambda_ij))
488 + this->get_a1Shatij(zeta_x_eff, lambda_ij)
489 )
490 );
491 };
492 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);
493 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);
494 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);
495 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);
496
497 NumType a1ij = 2.0*MY_PI*rhos*dmat3(i,j)*epsilon_ij(i,j)*C_ij(i,j)*(
498 one_term(lambda_a_ij(i,j), zeta_x_eff_a) - one_term(lambda_r_ij(i,j), zeta_x_eff_r)
499 ); // divided by k_B
500
501 NumType contribution = xs(i)*xs(j)*a1ij;
502 double factor = (i == j) ? 1.0 : 2.0; // Off-diagonal terms contribute twice
503 a1kB += contribution*factor;
504
505 // --------------------------
506 // Calculations for a_2/k_B^2
507 // --------------------------
508
509 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);
510 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);
511 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);
512 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);
513 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);
514 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);
515
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;
517 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))*(
518 one_term(2.0*lambda_a_ij(i,j), zeta_x_eff_2a)
519 -2.0*one_term(lambda_a_ij(i,j)+lambda_r_ij(i,j), zeta_x_eff_ar)
520 +one_term(2.0*lambda_r_ij(i,j), zeta_x_eff_2r)
521 ); // divided by k_B^2
522
523 NumType contributiona2 = xs(i)*xs(j)*a2ij; // Eq. A19
524 a2kB2 += contributiona2*factor;
525
526 // --------------------------
527 // Calculations for a_3/k_B^3
528 // --------------------------
529 auto a3ij = -POW3(epsilon_ij(i,j))*fkij[4](i,j)*zeta_x_bar*exp(
530 fkij[5](i,j)*zeta_x_bar + fkij[6](i,j)*POW2(zeta_x_bar)
531 ); // divided by k_B^3
532 NumType contributiona3 = xs(i)*xs(j)*a3ij; // Eq. A25
533 a3kB3 += contributiona3*factor;
534
535 if (i == j){
536 // ------------------
537 // Chain contribution
538 // ------------------
539
540 // Eq. A29
541 auto gdHSii = exp(k0 + k1*x_0_ij + k2*POW2(x_0_ij) + k3*POW3(x_0_ij));
542
543 // The g1 terms
544 // ....
545
546 // This is the function for the second part (not the partial) that goes in g_{1,ii},
547 // divided by 2*PI*d_ij^3*epsilon*rhos
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));
550 };
551 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));
552
553 // Bhat = B*rho*kappa; diff(Bhat, rho) = Bhat + rho*dBhat/drho; kappa = 2*pi*eps*d^3
554 // This is the function for the partial derivative rhos*(da1ij/drhos),
555 // divided by 2*PI*d_ij^3*epsilon*rhos
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);
560 auto rhosdBdrhos = this->get_rhodBijdrho(zeta_x, X, I_, J_, Bhatij);
561 return forceeval(pow(x_0_ij, lambda_ij)*(rhosda1Sdrhos + rhosdBdrhos));
562 };
563 // This is rhos*d(a_1ij)/drhos/(2*pi*d^3*eps*rhos)
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)
567 );
568 auto g1ii = 3.0*da1iidrhos_term + g1_noderivterm;
569
570 // The g2 terms
571 // ....
572
573 // This is the second part (not the partial deriv.) that goes in g_{2,ii},
574 // divided by 2*PI*d_ij^3*epsilon*rhos
575 auto g2_noderivterm = -POW2(C_ij(i,i))*K_HS*(
576 lambda_a_ij(i,j)*one_term(2*lambda_a_ij(i,j), zeta_x_eff_2a)
577 -(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)
578 +lambda_r_ij(i,j)*one_term(2*lambda_r_ij(i,j), zeta_x_eff_2r)
579 );
580 // This is [rhos*d(a_2ij/(1+chi_ij))/drhos]/(2*pi*d^3*eps*rhos)
581 auto da2iidrhos_term = 0.5*POW2(C_ij(i,j))*(
582 rho_dK_HS_drho*(
583 one_term(2.0*lambda_a_ij(i,j), zeta_x_eff_2a)
584 -2.0*one_term(lambda_a_ij(i,j)+lambda_r_ij(i,j), zeta_x_eff_ar)
585 +one_term(2.0*lambda_r_ij(i,j), zeta_x_eff_2r))
586 +K_HS*(
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)
590 )
591 );
592 auto g2MCAij = 3.0*da2iidrhos_term + g2_noderivterm;
593
594 auto betaepsilon = epsilon_ij(i,i)/T; // (1/(kB*T))/epsilon
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)); // Eq. A37
598 auto g2ii = (1.0+gamma_cij)*g2MCAij;
599
600 NumType giiMie = gdHSii*exp((betaepsilon*g1ii + POW2(betaepsilon)*g2ii)/gdHSii);
601 alphar_chain -= molefracs[i]*(m[i]-1.0)*log(giiMie);
602 }
603 }
604 }
605
606 auto ahs = get_a_HS(rhos, zeta, D);
607 // Eq. A5 from Lafitte, multiplied by mbar
608 auto alphar_mono = forceeval(mbar*(ahs + a1kB/T + a2kB2/(T*T) + a3kB3/(T*T*T)));
609
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);
623 struct vals{
624 dmat_t dmat;
625 rhos_t rhos;
626 rhoN_t rhoN;
627 mbar_t mbar;
628 xs_t xs;
629 zeta_t zeta;
630 zeta_x_t zeta_x;
631 zeta_x_bar_t zeta_x_bar;
632 alphar_mono_t alphar_mono;
633 a1kB_t a1kB;
634 a2kB2_t a2kB2;
635 a3kB3_t a3kB3;
636 alphar_chain_t alphar_chain;
637 };
638 return vals{dmat, rhos, rhoN, mbar, xs, zeta, zeta_x, zeta_x_bar, alphar_mono, a1kB, a2kB2, a3kB3, alphar_chain};
639 }
640
642 template<typename RhoType>
643 auto get_KHS(const RhoType& pf) const {
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));
645 }
646
652 template<typename RhoType>
653 auto get_rhos_dK_HS_drhos(const RhoType& zeta_x) const {
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);
656 return forceeval(num/den*zeta_x);
657 }
658
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);
663 if (getbaseval(rhos) == 0){
664 /*
665 The limit of alphar_hs in the case of density going to zero is zero,
666 but its derivatives must still match so that the automatic differentiation tooling
667 will work properly, so a Taylor series around rho=0 is constructed. The first term is
668 needed for calculations of virial coefficient temperature derivatives.
669 The term zeta_0 in the denominator is zero, but *ratios* of zeta values are ok because
670 they cancel the rho (in the limit at least) so we can write that zeta_x/zeta_y = D_x/D_x where
671 D_i = sum_i x_im_id_{ii}. This allows for the substitution into the series expansion terms.
672
673 <sympy>
674 from sympy import *
675 zeta_0, zeta_1, zeta_2, zeta_3, rho = symbols('zeta_0, zeta_1, zeta_2, zeta_3, rho')
676 D_0, D_1, D_2, D_3 = symbols('D_0, D_1, D_2, D_3')
677 POW2 = lambda x: x**2
678 POW3 = lambda x: x**3
679 alpha = 1/zeta_0*(3*zeta_1*zeta_2/(1-zeta_3) + zeta_2**3/(zeta_3*POW2(1-zeta_3)) + (POW3(zeta_2)/POW2(zeta_3)-zeta_0)*log(1-zeta_3))
680 alpha = alpha.subs(zeta_0, rho*D_0).subs(zeta_1, rho*D_1).subs(zeta_2, rho*D_2).subs(zeta_3, rho*D_3)
681 for Nderiv in [1, 2, 3, 4, 5]:
682 display(simplify((simplify(diff(alpha, rho, Nderiv)).subs(rho,0)*rho**Nderiv/factorial(Nderiv)).subs(D_0, zeta_0/rho).subs(D_1, zeta_1/rho).subs(D_2, zeta_2/rho).subs(D_3, zeta_3/rho)))
683 </sympy>
684
685 The updated approach is simpler; start off with the expression for alphar_hs, and simplify
686 ratios where 0/0 for which the l'hopital limit would be ok until you remove all the terms
687 in the denominator, allowing it to be evaluated. All terms have a zeta_x/zeta_0 and a few
688 have zeta_3*zeta_0 in the denominator
689
690 */
691 auto Upsilon = 1.0 - zeta[3];
692 return forceeval(
693 3.0*D[1]/D[0]*zeta[2]/Upsilon
694 + D[2]*D[2]*zeta[2]/(D[3]*D[0]*Upsilon*Upsilon)
695 - log(Upsilon)
696 + (D[2]*D[2]*D[2])/(D[3]*D[3]*D[0])*log(Upsilon)
697 );
698 }
699 else{
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])));
701 }
702 }
703
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{
714 return forceeval(
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
716 );
717 }
718
731 template<typename ZetaType, typename IJ>
732 auto get_rhodBijdrho(const ZetaType& zeta_x, const ZetaType& /*one_minus_zeta_x3*/, 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);
735 }
736
748 template<typename ZetaType>
749 auto get_a1Shatij(const ZetaType& zeta_x_eff, double lambda_ij) const{
750 return forceeval(
751 -1.0/(lambda_ij-3.0)*(1.0-zeta_x_eff/2.0)/POW3(forceeval(1.0-zeta_x_eff))
752 );
753 }
754
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;
769 return forceeval(get_a1Shatij(zeta_x_eff, lambda_ij) + zetaxda1Shatdzetax);
770 }
771};
772
777private:
778
779 std::vector<std::string> names, bibtex;
781
782 static void check_kmat(const Eigen::ArrayXXd& kmat, Eigen::Index N) {
783 if (kmat.size() == 0){
784 return;
785 }
786 if (kmat.cols() != kmat.rows()) {
787 throw teqp::InvalidArgument("kmat rows and columns are not identical");
788 }
789 if (kmat.cols() != N) {
790 throw teqp::InvalidArgument("kmat needs to be a square matrix the same size as the number of components");
791 }
792 };
793 static auto get_coeffs_from_names(const std::vector<std::string> &names){
794 SAFTVRMieLibrary library;
795 return library.get_coeffs(names);
796 }
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);
801 }
802 return names_;
803 }
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);
808 }
809 return keys_;
810 }
811public:
812 static auto build_chain(const std::vector<SAFTVRMieCoeffs> &coeffs, const std::optional<Eigen::ArrayXXd>& kmat, const std::optional<nlohmann::json>& flags = std::nullopt){
813 if (kmat){
814 check_kmat(kmat.value(), coeffs.size());
815 }
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);
818 auto i = 0;
819 for (const auto &coeff : coeffs) {
820 m[i] = coeff.m;
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;
825 i++;
826 }
827 if (kmat){
828 return SAFTVRMieChainContributionTerms(m, epsilon_over_k, sigma_m, lambda_r, lambda_a, std::move(kmat.value()), flags);
829 }
830 else{
831 auto mat = Eigen::ArrayXXd::Zero(N,N);
832 return SAFTVRMieChainContributionTerms(m, epsilon_over_k, sigma_m, lambda_r, lambda_a, std::move(mat), flags);
833 }
834 }
835
836public:
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)) {};
839 SAFTVRMieNonpolarMixture(SAFTVRMieChainContributionTerms&& terms, const std::vector<SAFTVRMieCoeffs> &coeffs) : names(get_names(coeffs)), bibtex(get_bibtex(coeffs)), terms(std::move(terms)) {};
840
842
843 auto chain_factory(const std::vector<SAFTVRMieCoeffs> &coeffs, const std::optional<Eigen::ArrayXXd>& kmat){
844 return SAFTVRMieNonpolarMixture::build_chain(coeffs, kmat);
845 }
846
847 const auto& get_terms() const { return terms; }
848 auto get_core_calcs(double T, double rhomolar, const Eigen::ArrayXd& mole_fractions) const {
849 auto val = terms.get_core_calcs(T, rhomolar, mole_fractions);
850
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){
856 n[i].resize(N);
857 for (auto j = 0U; j < N; ++j){
858 n[i][j] = x(i,j);
859 }
860 }
861 return n;
862 };
863 return nlohmann::json{
864 {"dmat", fromArrayXX(val.dmat)},
865 {"rhos", val.rhos},
866 {"rhoN", val.rhoN},
867 {"mbar", val.mbar},
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},
873 {"a1kB", val.a1kB},
874 {"a2kB2", val.a2kB2},
875 {"a3kB3", val.a3kB3},
876 {"alphar_chain", val.alphar_chain}
877 };
878 }
879 auto get_names() const { return names; }
880 auto get_BibTeXKeys() const { return bibtex; }
881 auto get_m() const { return terms.m; }
882 auto get_sigma_Angstrom() const { return (terms.sigma_A).eval(); }
883 auto get_sigma_m() const { return terms.sigma_A/1e10; }
884 auto get_epsilon_over_k_K() const { return terms.epsilon_over_k; }
885 auto get_kmat() const { return terms.kmat; }
886 auto get_lambda_r() const { return terms.lambda_r; }
887 auto get_lambda_a() const { return terms.lambda_a; }
888 auto get_EPSKIJ_matrix() const { return terms.get_EPSKIJ_K_matrix(); }
889 auto get_SIGMAIJ_matrix() const { return terms.get_SIGMAIJ_m_matrix(); }
890
891 // template<typename VecType>
892 // double max_rhoN(const double T, const VecType& mole_fractions) const {
893 // auto N = mole_fractions.size();
894 // Eigen::ArrayX<double> d(N);
895 // for (auto i = 0; i < N; ++i) {
896 // d[i] = sigma_Angstrom[i] * (1.0 - 0.12 * exp(-3.0 * epsilon_over_k[i] / T));
897 // }
898 // return 6 * 0.74 / EIGEN_PI / (mole_fractions*m*powvec(d, 3)).sum()*1e30; // particles/m^3
899 // }
900
901 template<class VecType>
902 auto R(const VecType& molefrac) const {
904 }
905
906 template<typename TTYPE, typename RhoType, typename VecType>
907 auto alphar(const TTYPE& T, const RhoType& rhomolar, const VecType& mole_fractions) const {
908 // First values for the Mie chain with dispersion (always included)
909 error_if_expr(T); error_if_expr(rhomolar);
910 auto vals = terms.get_core_calcs(T, rhomolar, mole_fractions);
911 using type = std::common_type_t<TTYPE, RhoType, decltype(mole_fractions[0])>;
912 type alphar = vals.alphar_mono + vals.alphar_chain;
913
914 return forceeval(alphar);
915 }
916};
917
922private:
923
924 std::vector<std::string> names, bibtex;
926 const std::optional<SAFTpolar::multipolar_contributions_variant> polar; // Can be present or not
927
928 static void check_kmat(const Eigen::ArrayXXd& kmat, Eigen::Index N) {
929 if (kmat.size() == 0){
930 return;
931 }
932 if (kmat.cols() != kmat.rows()) {
933 throw teqp::InvalidArgument("kmat rows and columns are not identical");
934 }
935 if (kmat.cols() != N) {
936 throw teqp::InvalidArgument("kmat needs to be a square matrix the same size as the number of components");
937 }
938 };
939 static auto get_coeffs_from_names(const std::vector<std::string> &names){
940 SAFTVRMieLibrary library;
941 return library.get_coeffs(names);
942 }
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);
947 }
948 return names_;
949 }
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);
954 }
955 return keys_;
956 }
957public:
958 static auto build_chain(const std::vector<SAFTVRMieCoeffs> &coeffs, const std::optional<Eigen::ArrayXXd>& kmat, const std::optional<nlohmann::json>& flags = std::nullopt){
959 if (kmat){
960 check_kmat(kmat.value(), coeffs.size());
961 }
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);
964 auto i = 0;
965 for (const auto &coeff : coeffs) {
966 m[i] = coeff.m;
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;
971 i++;
972 }
973 if (kmat){
974 return SAFTVRMieChainContributionTerms(m, epsilon_over_k, sigma_m, lambda_r, lambda_a, std::move(kmat.value()), flags);
975 }
976 else{
977 auto mat = Eigen::ArrayXXd::Zero(N,N);
978 return SAFTVRMieChainContributionTerms(m, epsilon_over_k, sigma_m, lambda_r, lambda_a, std::move(mat), flags);
979 }
980 }
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());
983 auto i = 0;
984 for (const auto &coeff : coeffs) {
985 mustar2[i] = coeff.mustar2;
986 nmu[i] = coeff.nmu;
987 Qstar2[i] = coeff.Qstar2;
988 nQ[i] = coeff.nQ;
989 i++;
990 }
991 bool has_dipolar = ((mustar2*nmu).cwiseAbs().sum() == 0);
992 bool has_quadrupolar = ((Qstar2*nQ).cwiseAbs().sum() == 0);
993 if (!has_dipolar && !has_quadrupolar){
994 return std::nullopt; // No dipolar or quadrupolar contribution is present
995 }
996 else{
997 // The dispersive and hard chain initialization has already happened at this point
998 return saft::polar_terms::GrossVrabec::MultipolarContributionGrossVrabec(terms.m, terms.sigma_A, terms.epsilon_over_k, mustar2, nmu, Qstar2, nQ);
999 }
1000 }
1001
1002public:
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)) {};
1005 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)) {};
1006
1007
1008// PCSAFTMixture( const PCSAFTMixture& ) = delete; // non construction-copyable
1009 SAFTVRMieMixture& operator=( const SAFTVRMieMixture& ) = delete; // non copyable
1010
1011 auto chain_factory(const std::vector<SAFTVRMieCoeffs> &coeffs, const std::optional<Eigen::ArrayXXd>& kmat){
1012 SAFTVRMieMixture::build_chain(coeffs, kmat);
1013 }
1014
1015 const auto& get_polar() const { return polar; }
1016
1017 // Checker for whether a polar term is present
1018 bool has_polar() const{ return polar.has_value(); }
1019
1020 const auto& get_terms() const { return terms; }
1021 auto get_core_calcs(double T, double rhomolar, const Eigen::ArrayXd& mole_fractions) const {
1022 auto val = terms.get_core_calcs(T, rhomolar, mole_fractions);
1023
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){
1029 n[i].resize(N);
1030 for (auto j = 0U; j < N; ++j){
1031 n[i][j] = x(i,j);
1032 }
1033 }
1034 return n;
1035 };
1036 return nlohmann::json{
1037 {"dmat", fromArrayXX(val.dmat)},
1038 {"rhos", val.rhos},
1039 {"rhoN", val.rhoN},
1040 {"mbar", val.mbar},
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},
1046 {"a1kB", val.a1kB},
1047 {"a2kB2", val.a2kB2},
1048 {"a3kB3", val.a3kB3},
1049 {"alphar_chain", val.alphar_chain}
1050 };
1051 }
1052 auto get_names() const { return names; }
1053 auto get_BibTeXKeys() const { return bibtex; }
1054 auto get_m() const { return terms.m; }
1055 auto get_sigma_Angstrom() const { return (terms.sigma_A).eval(); }
1056 auto get_sigma_m() const { return terms.sigma_A/1e10; }
1057 auto get_epsilon_over_k_K() const { return terms.epsilon_over_k; }
1058 auto get_kmat() const { return terms.kmat; }
1059 auto get_lambda_r() const { return terms.lambda_r; }
1060 auto get_lambda_a() const { return terms.lambda_a; }
1061 auto get_EPSKIJ_matrix() const { return terms.get_EPSKIJ_K_matrix(); }
1062 auto get_SIGMAIJ_matrix() const { return terms.get_SIGMAIJ_m_matrix(); }
1063
1064 // template<typename VecType>
1065 // double max_rhoN(const double T, const VecType& mole_fractions) const {
1066 // auto N = mole_fractions.size();
1067 // Eigen::ArrayX<double> d(N);
1068 // for (auto i = 0; i < N; ++i) {
1069 // d[i] = sigma_Angstrom[i] * (1.0 - 0.12 * exp(-3.0 * epsilon_over_k[i] / T));
1070 // }
1071 // return 6 * 0.74 / EIGEN_PI / (mole_fractions*m*powvec(d, 3)).sum()*1e30; // particles/m^3
1072 // }
1073
1074 template<class VecType>
1075 auto R(const VecType& molefrac) const {
1077 }
1078
1079 template<typename TTYPE, typename RhoType, typename VecType>
1080 auto alphar(const TTYPE& T, const RhoType& rhomolar, const VecType& mole_fractions) const {
1081 // First values for the Mie chain with dispersion (always included)
1082 error_if_expr(T); error_if_expr(rhomolar);
1083 auto vals = terms.get_core_calcs(T, rhomolar, mole_fractions);
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];
1087
1088 if (polar){ // polar term is present
1090 auto visitor = [&T, &rhomolar, &mole_fractions, &packing_fraction](const auto& contrib) -> type {
1091
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;
1096 return alpha;
1097 }
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;
1102 }
1103 else{
1104 throw teqp::InvalidArgument("Don't know how to handle this kind of arguments in polar term");
1105 }
1106 };
1107 alphar += std::visit(visitor, polar.value());
1108 }
1109
1110 return forceeval(alphar);
1111 }
1112};
1113
1114inline auto SAFTVRMieNonpolarfactory(const nlohmann::json & spec){
1115
1116 using klass = SAFTVRMieNonpolarMixture;
1117 std::optional<Eigen::ArrayXXd> kmat;
1118 if (spec.contains("kmat") && spec.at("kmat").is_array() && spec.at("kmat").size() > 0){
1119 kmat = build_square_matrix(spec["kmat"]);
1120 }
1121
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()));
1126 }
1127 return klass(names, kmat);
1128 }
1129 else if (spec.contains("coeffs")){
1130 std::vector<SAFTVRMieCoeffs> coeffs;
1131 for (auto j : spec["coeffs"]) {
1133 c.name = j.at("name");
1134 c.m = j.at("m");
1135 c.sigma_m = (j.contains("sigma_m")) ? j.at("sigma_m").get<double>() : j.at("sigma_Angstrom").get<double>()/1e10;
1136 c.epsilon_over_k = j.at("epsilon_over_k");
1137 c.lambda_r = j.at("lambda_r");
1138 c.lambda_a = j.at("lambda_a");
1139 c.BibTeXKey = j.at("BibTeXKey");
1140 coeffs.push_back(c);
1141 }
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()));
1144 }
1145 return klass(klass::build_chain(coeffs, kmat), coeffs);
1146 }
1147 else{
1148 throw std::invalid_argument("you must provide names or coeffs, but not both");
1149 }
1150}
1151
1152inline auto SAFTVRMiefactory(const nlohmann::json & spec){
1153
1154 std::optional<Eigen::ArrayXXd> kmat;
1155 if (spec.contains("kmat") && spec.at("kmat").is_array() && spec.at("kmat").size() > 0){
1156 kmat = build_square_matrix(spec["kmat"]);
1157 }
1158
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()));
1163 }
1164 return SAFTVRMieMixture(names, kmat);
1165 }
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");
1172 c.m = j.at("m");
1173 c.sigma_m = (j.contains("sigma_m")) ? j.at("sigma_m").get<double>() : j.at("sigma_Angstrom").get<double>()/1e10;
1174 c.epsilon_over_k = j.at("epsilon_over_k");
1175 c.lambda_r = j.at("lambda_r");
1176 c.lambda_a = j.at("lambda_a");
1177 c.BibTeXKey = j.at("BibTeXKey");
1178
1179 // These are legacy definitions of the polar moments
1180 if (j.contains("(mu^*)^2") && j.contains("nmu")){
1181 c.mustar2 = j.at("(mu^*)^2");
1182 c.nmu = j.at("nmu");
1183 something_polar = true;
1184 }
1185 if (j.contains("(Q^*)^2") && j.contains("nQ")){
1186 c.Qstar2 = j.at("(Q^*)^2");
1187 c.nQ = j.at("nQ");
1188 something_polar = true;
1189 }
1190 if (j.contains("Q_Cm2") || j.contains("Q_DA") || j.contains("mu_Cm") || j.contains("mu_D")){
1191 something_polar = true;
1192 }
1193 coeffs.push_back(c);
1194 }
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()));
1197 }
1198
1199 if (!something_polar){
1200 // Nonpolar, just m, epsilon, sigma and possibly a kmat matrix with kij coefficients
1201 return SAFTVRMieMixture(SAFTVRMieMixture::build_chain(coeffs, kmat), coeffs);
1202 }
1203 else{
1204 // Polar term is also provided, along with the chain terms
1205 std::string polar_model = "GrossVrabec"; // This is the default, as it was the first one implemented
1206 if (spec.contains("polar_model")){
1207 polar_model = spec["polar_model"];
1208 }
1209 std::optional<nlohmann::json> SAFTVRMie_flags = std::nullopt;
1210 if (spec.contains("SAFTVRMie_flags")){
1211 SAFTVRMie_flags = spec["SAFTVRMie_flags"];
1212 }
1213 std::optional<nlohmann::json> polar_flags = std::nullopt;
1214 if (spec.contains("polar_flags")){
1215 polar_flags = spec["polar_flags"];
1216 }
1217 else{
1218 // Set to the default value
1219 polar_flags = nlohmann::json{{"approach", "use_packing_fraction"}};
1220 }
1221
1222 // Go back and extract the dipolar and quadrupolar terms from
1223 // the JSON, in base SI units
1224 const double D_to_Cm = 3.33564e-30; // C m/D
1225 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)
1226 const double Qstar2factor = 1.0/(4*static_cast<double>(EIGEN_PI)*8.8541878128e-12*1.380649e-23); // same as mustar2factor
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);
1229 Eigen::Index i = 0;
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")){
1236 // Terms defined like in Gross&Vrabec; backwards-compatibility
1237 double mustar2 = j.at("(mu^*)^2");
1238 return sqrt(mustar2*(m*epsilon_over_k*pow(sigma_m, 3))/mustar2factor);
1239 }
1240 else if (j.contains("mu_Cm")){
1241 return j.at("mu_Cm");
1242 }
1243 else if (j.contains("mu_D")){
1244 return j.at("mu_D").get<double>()*D_to_Cm;
1245 }
1246 else{
1247 return 0.0;
1248 }
1249 };
1250 auto get_quadrupole_Cm2 = [&]() -> double{
1251 if (j.contains("(Q^*)^2") && j.contains("nQ")){
1252 // Terms defined like in Gross&Vrabec; backwards-compatibility
1253 double Qstar2 = j.at("(Q^*)^2");
1254 return sqrt(Qstar2*(m*epsilon_over_k*pow(sigma_m, 5))/Qstar2factor);
1255 }
1256 else if (j.contains("Q_Cm2")){
1257 return j.at("Q_Cm2");
1258 }
1259 else if (j.contains("Q_DA")){
1260 return j.at("Q_DA").get<double>()*D_to_Cm/1e10;
1261 }
1262 else{
1263 return 0.0;
1264 }
1265 };
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);
1269 i++;
1270 };
1271 nlohmann::json SAFTVRMieFlags;
1272
1273 auto chain = SAFTVRMieMixture::build_chain(coeffs, kmat, SAFTVRMie_flags);
1274 auto EPSKIJ = chain.get_EPSKIJ_K_matrix(); // In units of K
1275 auto SIGMAIJ = chain.get_SIGMAIJ_m_matrix(); // In units of m
1276
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();
1281 auto polar = saft::polar_terms::GrossVrabec::MultipolarContributionGrossVrabec(ms, sigma_ms*1e10, epsks, mustar2, nmu, Qstar2, nQ);
1282 return SAFTVRMieMixture(std::move(chain), coeffs, std::move(polar));
1283 }
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);
1289 return SAFTVRMieMixture(std::move(chain), coeffs, std::move(polar));
1290 }
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);
1296 return SAFTVRMieMixture(std::move(chain), coeffs, std::move(polar));
1297 }
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);
1303 return SAFTVRMieMixture(std::move(chain), coeffs, std::move(polar));
1304 }
1305
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);
1309 return SAFTVRMieMixture(std::move(chain), coeffs, std::move(polar));
1310 }
1311// if (polar_model == "GrayGubbins+Gottschalk"){
1312// using MCGG = MultipolarContributionGrayGubbins<GottschalkJIntegral, GottschalkKIntegral>;
1313// auto polar = MCGG(sigma_ms, epsks, mu_Cm, Q_Cm2, polar_flags);
1314// return SAFTVRMieMixture(std::move(chain), std::move(polar));
1315// }
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);
1319 return SAFTVRMieMixture(std::move(chain), coeffs, std::move(polar));
1320 }
1321
1322
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);
1328 return SAFTVRMieMixture(std::move(chain), coeffs, std::move(polar));
1329 }
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);
1335 return SAFTVRMieMixture(std::move(chain), coeffs, std::move(polar));
1336 }
1337 throw teqp::InvalidArgument("didn't understand this polar_model:"+polar_model);
1338 }
1339 }
1340 else{
1341 throw std::invalid_argument("you must provide names or coeffs, but not both");
1342 }
1343}
1344
1345} /* namespace SAFTVRMie */
1346}; // 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
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)
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 alphar(const TTYPE &T, const RhoType &rhomolar, const VecType &mole_fractions) const
auto R(const VecType &molefrac) const
auto chain_factory(const std::vector< SAFTVRMieCoeffs > &coeffs, const std::optional< Eigen::ArrayXXd > &kmat)
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)
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
#define X(f)
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.
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:90
void error_if_expr(T &&)
Definition types.hpp:69
T powi(const T &x, int n)
From Ulrich Deiters.
Definition types.hpp:139
auto quad(const std::function< T(Double)> &F, const Double &a, const Double &b)
auto pow(const double &x, const double &e)
Definition types.hpp:195
auto forceeval(T &&expr)
Definition types.hpp:52
auto get_R_gas()
< Gas constant, according to CODATA 2019, in the given number type
Definition constants.hpp:22
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.
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)
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_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