teqp 0.22.0
Loading...
Searching...
No Matches
COSMOSAC.hpp
Go to the documentation of this file.
1
10
17public:
18 Eigen::ArrayXd m_sigma, m_psigmaA;
22 SigmaProfile(const Eigen::ArrayXd &sigma, const Eigen::ArrayXd &psigmaA) : m_sigma(sigma), m_psigmaA(psigmaA) {};
24 SigmaProfile(const std::vector<double> &sigma, const std::vector<double> &psigmaA) : m_sigma(Eigen::Map<const Eigen::ArrayXd>(&(sigma[0]), sigma.size())), m_psigmaA(Eigen::Map<const Eigen::ArrayXd>(&(psigmaA[0]), psigmaA.size())) {};
26 SigmaProfile(Eigen::ArrayXd &&sigma, Eigen::ArrayXd &&psigmaA) : m_sigma(sigma), m_psigmaA(psigmaA) {};
27 const Eigen::ArrayXd &psigmaA() const { return m_psigmaA; }
28 const Eigen::ArrayXd &sigma() const { return m_sigma; }
29 const Eigen::ArrayXd psigma(double A_i) const { return m_psigmaA/A_i; }
30};
31
32// A holder class for the sigma profiles for a given fluid
38
40 double q0 = 79.53, // [A^2]
41 r0 = 66.69, // [A^3]
43 std::string to_string() {
44 return "q0: " + std::to_string(q0) + " A^2 \nr0: " + std::to_string(r0) + " A^3\nz_coordination: " + std::to_string(z_coordination);
45 }
46};
47
48
49
51 double
52 AEFFPRIME = 7.25, // [A^2]
53 c_OH_OH = 4013.78, // [kcal A^4/(mol e^2)]
54 c_OT_OT = 932.31, // [kcal A^4 /(mol e^2)]
55 c_OH_OT = 3016.43, // [kcal A^4 /(mol e^2)]
56 A_ES = 6525.69, // [kcal A^4 /(mol e^2)]
57 B_ES = 1.4859e8, // [kcal A^4 K^2/(mol e^2)]
58 N_A = 6.022140758e23, // [mol^{-1}]
59 k_B = 1.38064903e-23, // [J K^{-1}]
60 R = k_B*N_A/4184, // [kcal/(mol*K)]; Universal gas constant of new redefinition of 2018, https://doi.org/10.1088/1681-7575/aa99bc
61 Gamma_rel_tol = 1e-8; // relative tolerance for Gamma in iterative loop
62 bool fast_Gamma = true;
63 std::string to_string() {
64 return "NOT IMPLEMENTED YET";
65 //return "c_hb: " + std::to_string(c_hb) + " kcal A^4 /(mol*e^2) \nsigma_hb: " + std::to_string(sigma_hb) + " e/A^2\nalpha_prime: " + std::to_string(alpha_prime) + " kcal A^4 /(mol*e^2)\nAEFFPRIME: " + std::to_string(AEFFPRIME) + " A\nR: " + std::to_string(R) + " kcal/(mol*K)";
66 }
67};
68
70
71class COSMO3 {
72private:
73 std::vector<double> A_COSMO_A2;
74 std::vector<double> V_COSMO_A3;
75 std::vector<FluidSigmaProfiles> profiles;
76 COSMO3Constants m_consts;
78 Eigen::Index ileft, w;
79public:
80 COSMO3(const std::vector<double>& A_COSMO_A2, const std::vector<double>& V_COSMO_A3, const std::vector<FluidSigmaProfiles> &SigmaProfiles, const COSMO3Constants &constants = COSMO3Constants(), const CombinatorialConstants &comb_constants = CombinatorialConstants())
81 : A_COSMO_A2(A_COSMO_A2), V_COSMO_A3(V_COSMO_A3), profiles(SigmaProfiles), m_consts(constants), m_comb_consts(comb_constants) {
82 Eigen::Index iL, iR;
83 std::tie(iL, iR) = get_nonzero_bounds();
84 this->ileft = iL; this->w = iR - iL + 1;
85 };
86
88 std::tuple<Eigen::Index, Eigen::Index> get_nonzero_bounds(){
89 // Determine the range of entries in p(sigma) that are greater than zero, we
90 // will only calculate segment activity coefficients for these segments
91 Eigen::Index min_ileft = 51, max_iright = 0;
92 for (auto i = 0U; i < profiles.size(); ++i) {
93 const Eigen::ArrayXd psigma = profiles[i].nhb.psigma(A_COSMO_A2[i]);
94 Eigen::Index ileft = 0, iright = psigma.size();
95 for (auto ii = 0; ii < psigma.size(); ++ii) { if (std::abs(psigma(ii)) > 1e-16) { ileft = ii; break; } }
96 for (auto ii = psigma.size() - 1; ii > ileft; --ii) { if (std::abs(psigma(ii)) > 1e-16) { iright = ii; break; } }
97 if (ileft < min_ileft) { min_ileft = ileft; }
98 if (iright > max_iright) { max_iright = iright; }
99 }
100 return std::make_tuple(min_ileft, max_iright);
101 }
102
103 template<typename MoleFractions>
104 auto get_psigma_mix(const MoleFractions &z, profile_type type = profile_type::NHB_PROFILE) const {
105 Eigen::ArrayX<std::decay_t<decltype(z[0])>> psigma_mix(51); psigma_mix.fill(0);
106 std::decay_t<decltype(z[0])> xA = 0.0;
107 for (auto i = 0; i < z.size(); ++i) {
108 switch (type) {
110 psigma_mix += z[i] * profiles[i].nhb.psigmaA(); break;
112 psigma_mix += z[i] * profiles[i].oh.psigmaA(); break;
114 psigma_mix += z[i] * profiles[i].ot.psigmaA(); break;
115 }
116 xA += z[i] * A_COSMO_A2[i];
117 }
118 psigma_mix /= xA;
119 return psigma_mix;
120 }
121
124
125 std::tuple<Eigen::Index, Eigen::Index> get_ileftw() const { return std::make_tuple(ileft, w);} ;
126
127 double get_c_hb(profile_type type1, profile_type type2) const{
128
129 if (type1 == type2){
130 if (type1 == profile_type::OH_PROFILE) {
131 return m_consts.c_OH_OH;
132 }
133 else if (type1 == profile_type::OT_PROFILE) {
134 return m_consts.c_OT_OT;
135 }
136 else {
137 return 0.0;
138 }
139 }
140 else if ((type1 == profile_type::OH_PROFILE && type2 == profile_type::OT_PROFILE)
141 || (type1 == profile_type::OT_PROFILE && type2 == profile_type::OH_PROFILE)) {
142 return m_consts.c_OH_OT;
143 }
144 else {
145 return 0.0;
146 }
147 }
148 template<typename TType>
149 auto get_DELTAW(const TType& T, profile_type type_t, profile_type type_s) const {
150 auto delta_sigma = 2*0.025/50;
151 Eigen::ArrayXX<TType> DELTAW(51, 51);
152 double cc = get_c_hb(type_t, type_s);
153 for (auto m = 0; m < 51; ++m) {
154 for (auto n = 0; n < 51; ++n) {
155 double sigma_m = -0.025 + delta_sigma*m,
156 sigma_n = -0.025 + delta_sigma*n,
157 c_hb = (sigma_m*sigma_n >= 0) ? 0 : cc;
158 auto c_ES = m_consts.A_ES + m_consts.B_ES/(T*T);
159 DELTAW(m, n) = c_ES*POW2(sigma_m + sigma_n) - c_hb*POW2(sigma_m-sigma_n);
160 }
161 }
162 return DELTAW;
163 }
164 template<typename TType>
165 auto get_DELTAW_fast(TType T, profile_type type_t, profile_type type_s) const {
166 auto delta_sigma = 2 * 0.025 / 50;
167 Eigen::ArrayXX<TType> DELTAW(51, 51); DELTAW.setZero();
168 double cc = get_c_hb(type_t, type_s);
169 for (auto m = ileft; m < ileft+w+1; ++m) {
170 for (auto n = ileft; n < ileft+w+1; ++n) {
171 double sigma_m = -0.025 + delta_sigma*m,
172 sigma_n = -0.025 + delta_sigma*n,
173 c_hb = (sigma_m*sigma_n >= 0) ? 0 : cc;
174 auto c_ES = m_consts.A_ES + m_consts.B_ES / (T*T);
175 DELTAW(m, n) = c_ES*POW2(sigma_m + sigma_n) - c_hb*POW2(sigma_m - sigma_n);
176 }
177 }
178 return DELTAW;
179 }
180
181 template<typename TType, typename PSigma>
182 auto get_AA(const TType& T, PSigma psigmas){
183 // Build the massive \Delta W matrix that is 153*153 in size
184 Eigen::ArrayXX<TType> DELTAW(51, 51); // Depends on temperature
185 double R = m_consts.R;
187 for (auto i = 0; i < 3; ++i) {
188 for (auto j = 0; j < 3; ++j) {
189 DELTAW.matrix().block(51 * i, 51 * j, 51, 51) = get_DELTAW(T, types[i], types[j]);
190 }
191 }
192 return Eigen::exp(-DELTAW/(R*T)).rowwise()*psigmas.transpose();
193 }
202 template<typename TType, typename PSigmaType>
203 auto get_Gamma(const TType& T, PSigmaType psigmas) const {
204 //auto startTime = std::chrono::high_resolution_clock::now();
205
206 using TXType = std::decay_t<std::common_type_t<TType, decltype(psigmas[0])>>;
207
208 double R = m_consts.R;
209 Eigen::ArrayX<TXType> Gamma(153), Gammanew(153); Gamma.setOnes(); Gammanew.setOnes();
210
211 // A convenience function to convert double values to string in scientific format
212 auto to_scientific = [](double val) { std::ostringstream out; out << std::scientific << val; return out.str(); };
213
214 auto max_iter = 500;
215 if (!m_consts.fast_Gamma){
216 // The slow and simple branch
217
218 // Build the massive \Delta W matrix that is 153*153 in size
219 Eigen::ArrayXX<TType> DELTAW(153, 153);
221 for (auto i = 0; i < 3; ++i) {
222 for (auto j = 0; j < 3; ++j) {
223 DELTAW.matrix().block(51*i, 51*j, 51, 51) = get_DELTAW(T, types[i], types[j]);
224 }
225 }
226
227 auto AA = (Eigen::exp(-DELTAW / (R*T)).template cast<TXType>().rowwise()*psigmas.template cast<TXType>().transpose()).eval();
228 for (auto counter = 0; counter <= max_iter; ++counter) {
229 Gammanew = 1 / (AA.rowwise()*Gamma.transpose()).rowwise().sum();
230 Gamma = (Gamma + Gammanew) / 2;
231 double maxdiff = getbaseval(((Gamma - Gammanew) / Gamma).cwiseAbs().real().maxCoeff());
232 if (maxdiff < m_consts.Gamma_rel_tol) {
233 break;
234 }
235 if (counter == max_iter) {
236 throw std::invalid_argument("Could not obtain the desired tolerance of "
237 + to_scientific(m_consts.Gamma_rel_tol)
238 + " after "
239 + std::to_string(max_iter)
240 + " iterations in get_Gamma; current value is "
241 + to_scientific(maxdiff));
242 }
243 }
244 return Gamma;
245 }
246 else {
247 // The fast branch!
248 // ----------------
249
250 // Build the massive AA matrix that is 153*153 in size
251 //auto midTime = std::chrono::high_resolution_clock::now();
253 std::vector<Eigen::Index> offsets = {0*51, 1*51, 2*51};
254 Eigen::ArrayXX<TXType> AA(153, 153);
255 for (auto i = 0; i < 3; ++i) {
256 for (auto j = 0; j < 3; ++j) {
257 Eigen::Index rowoffset = offsets[i], coloffset = offsets[j];
258 AA.matrix().block(rowoffset + ileft, coloffset + ileft, w, w) = Eigen::exp(-get_DELTAW_fast(T, types[i], types[j]).block(ileft, ileft, w, w).array() / (R*T)).template cast<TXType>().rowwise()*psigmas.template cast<TXType>().segment(coloffset+ileft,w).transpose();
259 }
260 }
261 //auto midTime2 = std::chrono::high_resolution_clock::now();
262
263 for (auto counter = 0; counter <= max_iter; ++counter) {
264 for (Eigen::Index offset : {51*0, 51*1, 51*2}){
265 Gammanew.segment(offset + ileft, w) = 1 / (
266 AA.matrix().block(offset+ileft,51*0+ileft,w,w).array().rowwise()*Gamma.segment(51*0+ileft, w).transpose()
267 + AA.matrix().block(offset+ileft,51*1+ileft,w,w).array().rowwise()*Gamma.segment(51*1+ileft, w).transpose()
268 + AA.matrix().block(offset+ileft,51*2+ileft,w,w).array().rowwise()*Gamma.segment(51*2+ileft, w).transpose()
269 ).rowwise().sum();
270 }
271 for (Eigen::Index offset : {51 * 0, 51 * 1, 51 * 2}) {
272 Gamma.segment(offset + ileft, w) = (Gamma.segment(offset + ileft, w) + Gammanew.segment(offset + ileft, w)) / 2;
273 }
274 double maxdiff = getbaseval(((Gamma - Gammanew) / Gamma).cwiseAbs().real().maxCoeff());
275 if (maxdiff < m_consts.Gamma_rel_tol) {
276 break;
277 }
278 if (!std::isfinite(maxdiff)){
279 throw teqp::InvalidArgument("Gammas are not finite");
280 }
281 if (counter == max_iter){
282 throw std::invalid_argument("Could not obtain the desired tolerance of "
283 + to_scientific(m_consts.Gamma_rel_tol)
284 +" after "
285 +std::to_string(max_iter)
286 +" iterations in get_Gamma; current value is "
287 + to_scientific(maxdiff));
288 }
289 }
290 //auto endTime = std::chrono::high_resolution_clock::now();
291 //std::cout << std::chrono::duration<double>(midTime - startTime).count() << " s elapsed (DELTAW)\n";
292 //std::cout << std::chrono::duration<double>(midTime2 - midTime).count() << " s elapsed (AA)\n";
293 //std::cout << std::chrono::duration<double>(endTime - midTime2).count() << " s elapsed (comps)\n";
294 //std::cout << std::chrono::duration<double>(endTime - startTime).count() << " s elapsed (total)\n";
295 return Gamma;
296 }
297 }
298
302 template<typename TType, typename Array>
303 auto get_lngamma_resid(std::size_t i, TType T, const Array &lnGamma_mix) const
304 {
305 double AEFFPRIME = m_consts.AEFFPRIME;
306 Eigen::ArrayX<double> psigmas(3*51); // For a pure fluid, p(sigma) does not depend on temperature
307 double A_i = A_COSMO_A2[i];
308 psigmas << profiles[i].nhb.psigma(A_i), profiles[i].oh.psigma(A_i), profiles[i].ot.psigma(A_i);
309 // double check_sum = psigmas.sum(); //// Should sum to 1.0
310 auto lnGammai = get_Gamma(T, psigmas).log().eval();
311 return A_i/AEFFPRIME*(psigmas*(lnGamma_mix - lnGammai)).sum();
312 }
313
321 template<typename TType, typename MoleFracs>
322 auto get_lngamma_resid(TType T, const MoleFracs& molefracs) const
323 {
324 using TXType = std::decay_t<std::common_type_t<TType, decltype(molefracs[0])>>;
325
326 Eigen::Array<TXType, 153, 1> psigmas;
328
329 Eigen::ArrayX<TXType> lngamma(molefracs.size());
330 // double check_sum = psigmas.sum(); //// Should sum to 1.0
331 Eigen::ArrayX<TXType> lnGamma_mix = get_Gamma(T, psigmas).log();
332 for (Eigen::Index i = 0; i < molefracs.size(); ++i) {
333 lngamma(i) = get_lngamma_resid(i, T, lnGamma_mix);
334 }
335 return lngamma;
336 }
337 template<typename TType, typename MoleFracs>
338 auto calc_lngamma_resid(TType T, const MoleFracs& molefracs) const
339 {
340 return get_lngamma_resid(T, molefracs);
341 }
342
346 template<typename TType, typename MoleFractions>
347 auto calc_lngamma_comb(const TType& /*T*/, const MoleFractions &x) const {
348 double q0 = m_comb_consts.q0,
349 r0 = m_comb_consts.r0,
350 z_coordination = m_comb_consts.z_coordination;
351 std::decay_t<MoleFractions> q = Eigen::Map<const Eigen::ArrayXd>(&(A_COSMO_A2[0]), A_COSMO_A2.size()) / q0,
352 r = Eigen::Map<const Eigen::ArrayXd>(&(V_COSMO_A3[0]), V_COSMO_A3.size()) / r0,
353 l = z_coordination / 2 * (r - q) - (r - 1),
354 phi_over_x = r / contiguous_dotproduct(x, r),
355 phi = x * phi_over_x,
356 theta_over_x = q / contiguous_dotproduct(x, q),
357 theta = x * theta_over_x,
358 theta_over_phi = theta_over_x/phi_over_x;
359
360 // Eq. 15 from Bell, JCTC, 2020
361 return ((log(phi_over_x) + z_coordination / 2 * q * log(theta_over_phi) + l - phi_over_x * contiguous_dotproduct(x, l))).eval();
362 }
363
364 // EigenArray get_lngamma_disp(const EigenArray &x) const {
365 // if (x.size() != 2){ throw std::invalid_argument("Multi-component mixtures not supported for dispersive contribution yet"); }
366 // double w = 0.27027; // default value
367 // auto cls0 = m_fluids[0].dispersion_flag, cls1 = m_fluids[1].dispersion_flag;
368 // using d = FluidProfiles::dispersion_classes;
369 // if ((cls0 == d::DISP_WATER && cls1 == d::DISP_ONLY_ACCEPTOR)
370 // |
371 // (cls1 == d::DISP_WATER && cls0 == d::DISP_ONLY_ACCEPTOR)){
372 // w = -0.27027;
373 // }
374 // else if ((cls0 == d::DISP_WATER && cls1 == d::DISP_COOH)
375 // |
376 // (cls1 == d::DISP_WATER && cls0 == d::DISP_COOH)) {
377 // w = -0.27027;
378 // }
379 // else if ((cls0 == d::DISP_COOH && (cls1 == d::DISP_NHB || cls1 == d::DISP_DONOR_ACCEPTOR))
380 // |
381 // (cls1 == d::DISP_COOH && (cls0 == d::DISP_NHB || cls0 == d::DISP_DONOR_ACCEPTOR))) {
382 // w = -0.27027;
383 // }
384 //
385 // double ekB0 = m_fluids[0].dispersion_eoverkB, ekB1 = m_fluids[1].dispersion_eoverkB;
386 // double A = w*(0.5*(ekB0+ekB1) - sqrt(ekB0*ekB1));
387 // EigenArray lngamma_dsp(2);
388 // lngamma_dsp(0) = A*x[1]*x[1];
389 // lngamma_dsp(1) = A*x[0]*x[0];
390 // return lngamma_dsp;
391 // }
392 // EigenArray get_lngamma(double T, const EigenArray &x) const override {
393 // return get_lngamma_comb(T, x) + get_lngamma_resid(T, x) + get_lngamma_disp(x);
394 // }
395
396 // Returns ares/RT
397 template<typename TType, typename MoleFractions>
398 auto operator () (const TType& T, const MoleFractions& molefracs) const {
399 return contiguous_dotproduct(molefracs, get_lngamma_resid(T, molefracs));
400 }
401};
402
403};
auto get_DELTAW_fast(TType T, profile_type type_t, profile_type type_s) const
Definition COSMOSAC.hpp:165
auto operator()(const TType &T, const MoleFractions &molefracs) const
Definition COSMOSAC.hpp:398
auto get_Gamma(const TType &T, PSigmaType psigmas) const
Definition COSMOSAC.hpp:203
auto calc_lngamma_comb(const TType &, const MoleFractions &x) const
Definition COSMOSAC.hpp:347
COSMO3Constants & get_mutable_COSMO_constants()
Get access to the parameters that are in use.
Definition COSMOSAC.hpp:123
auto get_psigma_mix(const MoleFractions &z, profile_type type=profile_type::NHB_PROFILE) const
Definition COSMOSAC.hpp:104
std::tuple< Eigen::Index, Eigen::Index > get_ileftw() const
Definition COSMOSAC.hpp:125
COSMO3(const std::vector< double > &A_COSMO_A2, const std::vector< double > &V_COSMO_A3, const std::vector< FluidSigmaProfiles > &SigmaProfiles, const COSMO3Constants &constants=COSMO3Constants(), const CombinatorialConstants &comb_constants=CombinatorialConstants())
Definition COSMOSAC.hpp:80
double get_c_hb(profile_type type1, profile_type type2) const
Definition COSMOSAC.hpp:127
auto get_lngamma_resid(TType T, const MoleFracs &molefracs) const
Definition COSMOSAC.hpp:322
auto get_lngamma_resid(std::size_t i, TType T, const Array &lnGamma_mix) const
Definition COSMOSAC.hpp:303
auto get_AA(const TType &T, PSigma psigmas)
Definition COSMOSAC.hpp:182
auto get_DELTAW(const TType &T, profile_type type_t, profile_type type_s) const
Definition COSMOSAC.hpp:149
std::tuple< Eigen::Index, Eigen::Index > get_nonzero_bounds()
Determine the left and right bounds for the psigma that is nonzero to accelerate the iterative calcul...
Definition COSMOSAC.hpp:88
auto calc_lngamma_resid(TType T, const MoleFracs &molefracs) const
Definition COSMOSAC.hpp:338
const Eigen::ArrayXd psigma(double A_i) const
Definition COSMOSAC.hpp:29
SigmaProfile(Eigen::ArrayXd &&sigma, Eigen::ArrayXd &&psigmaA)
Move constructor.
Definition COSMOSAC.hpp:26
SigmaProfile(const std::vector< double > &sigma, const std::vector< double > &psigmaA)
Copy-by-reference constructor with std::vector.
Definition COSMOSAC.hpp:24
SigmaProfile(const Eigen::ArrayXd &sigma, const Eigen::ArrayXd &psigmaA)
Copy-by-reference constructor.
Definition COSMOSAC.hpp:22
auto POW2(const A &x)
auto getbaseval(const T &expr)
Definition types.hpp:90
auto contiguous_dotproduct(const auto &x, const auto &y)
Take the dot-product of two vector-like objects that have contiguous memory and support the ....
Definition types.hpp:235
SigmaProfile ot
The profile for the "other" segments.
Definition COSMOSAC.hpp:36
SigmaProfile oh
The profile for the OH-bonding segments.
Definition COSMOSAC.hpp:35
SigmaProfile nhb
The profile for the non-hydrogen-bonding segments.
Definition COSMOSAC.hpp:34