teqp 0.22.0
Loading...
Searching...
No Matches
CPA.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <optional>
4#include <variant>
5
6#include "nlohmann/json.hpp"
7#include <Eigen/Dense>
8#include "teqp/types.hpp"
9#include "teqp/exceptions.hpp"
12
13namespace teqp {
14
15namespace CPA {
16
17template<typename X> auto POW2(X x) { return x * x; };
18template<typename X> auto POW3(X x) { return x * POW2(x); };
19
24
26template<typename BType, typename TType, typename RhoType, typename VecType>
27inline auto get_DeltaAB_pure(radial_dist dist, double epsABi, double betaABi, BType b_cubic, TType RT, RhoType rhomolar, const VecType& /*molefrac*/) {
28
29 using eta_type = std::common_type_t<decltype(rhomolar), decltype(b_cubic)>;
30 eta_type eta;
31 eta_type g_vm_ref;
32
33 // Calculate the contact value of the radial distribution function g(v)
34 switch (dist) {
35 case radial_dist::CS: {
36 // Carnahan - Starling EOS, given by Kontogeorgis et al., Ind.Eng.Chem.Res. 2006, 45, 4855 - 4868, Eq. 4a and 4b:
37 eta = (rhomolar / 4.0) * b_cubic;
38 g_vm_ref = (2.0 - eta) / (2.0 * POW3(1.0 - eta));
39 break;
40 }
41 case radial_dist::KG: {
42 // Function proposed by Kontogeorgis, G.M.; Yakoumis, I.V.; Meijer, H.; Hendriks, E.M.; Moorwood, T., Fluid Phase Equilib. 1999, 158 - 160, 201.
43 eta = (rhomolar / 4.0) * b_cubic;
44 g_vm_ref = 1.0 / (1.0 - 1.9 * eta);
45 break;
46 }
47// case radial_dist::OT: { // This is identical to KG
48// g_vm_ref = 1.0 / (1.0 - 0.475 * rhomolar * b_cubic);
49// break;
50// }
51 default: {
52 throw std::invalid_argument("Bad radial_dist");
53 }
54 }
55
56 // Calculate the association strength between site Ai and Bi for a pure compent
57 auto DeltaAiBj = forceeval(g_vm_ref*(exp(epsABi/RT) - 1.0)*b_cubic* betaABi);
58
59 return DeltaAiBj;
60};
61
66
67template<typename BType, typename TType, typename RhoType, typename VecType>
68inline auto XA_calc_pure(int N_sites, association_classes scheme, radial_dist dist, double epsABi, double betaABi, const BType b_cubic, const TType RT, const RhoType rhomolar, const VecType& molefrac) {
69
70 // Matrix XA(A, j) that contains all of the fractions of sites A not bonded to other active sites for each molecule i
71 // Start values for the iteration(set all sites to non - bonded, = 1)
72 using result_type = std::common_type_t<decltype(RT), decltype(rhomolar), decltype(molefrac[0])>;
73 Eigen::Array<result_type, Eigen::Dynamic, Eigen::Dynamic> XA; // A maximum of 4 association sites(A, B, C, D)
74 XA.resize(N_sites, 1);
75 XA.setOnes();
76
77 // Get the association strength between the associating sites
78 auto DeltaAiBj = get_DeltaAB_pure(dist, epsABi, betaABi, b_cubic, RT, rhomolar, molefrac);
79
80 if (scheme == association_classes::a1A) { // Acids
81 // Only one association site "A" (OH - group with C = O - group)
82 XA(0, 0) = forceeval((-1.0 + sqrt(1.0 + 4.0 * rhomolar * DeltaAiBj)) / (2.0 * rhomolar * DeltaAiBj));
83 }
84 else if (scheme == association_classes::a2B) { // Alcohols
85 // Two association sites "A" and "B"
86 XA(0, 0) = forceeval((-1.0 + sqrt(1.0 + 4.0 * rhomolar * DeltaAiBj)) / (2.0 * rhomolar * DeltaAiBj));
87 XA(1, 0) = XA(0, 0); // XB = XA;
88 }
89 else if (scheme == association_classes::a3B) { // Glycols
90 // Three association sites "A", "B", "C"
91 XA(0, 0) = forceeval((-(1.0 - rhomolar * DeltaAiBj) + sqrt(POW2(1.0 + rhomolar * DeltaAiBj) + 4.0 * rhomolar * DeltaAiBj)) / (4.0 * rhomolar * DeltaAiBj));
92 XA(1, 0) = XA(0, 0); // XB = XA
93 XA(2, 0) = 2.0*XA(0, 0) - 1.0; // XC = 2XA - 1
94 }
95 else if (scheme == association_classes::a4C) { // Water
96 // Four association sites "A", "B", "C", "D"
97 XA(0, 0) = forceeval((-1.0 + sqrt(1.0 + 8.0 * rhomolar * DeltaAiBj)) / (4.0 * rhomolar * DeltaAiBj));
98 XA(1, 0) = XA(0, 0); // XB = XA
99 XA(2, 0) = XA(0, 0); // XC = XA
100 XA(3, 0) = XA(0, 0); // XD = XA
101 }
102 else if (scheme == association_classes::not_associating) { // non - associating compound
103 XA(0, 0) = 1;
104 XA(1, 0) = 1;
105 XA(2, 0) = 1;
106 XA(3, 0) = 1;
107 }
108 else {
109 throw std::invalid_argument("Bad scheme");
110 }
111 return XA;
112};
113
114enum class cubic_flag {not_set, PR, SRK};
115inline auto get_cubic_flag(const std::string& s) {
116 if (s == "PR") { return cubic_flag::PR; }
117 else if (s == "SRK") { return cubic_flag::SRK; }
118 else {
119 throw std::invalid_argument("bad cubic flag:" + s);
120 }
121}
122
123
124class CPACubic {
125private:
126 const std::valarray<double> a0, bi, c1, Tc;
127 double delta_1, delta_2;
128 const double R_gas;
129 const std::optional<std::vector<std::vector<double>>> kmat;
130public:
131 CPACubic(cubic_flag flag, const std::valarray<double> &a0, const std::valarray<double> &bi, const std::valarray<double> &c1, const std::valarray<double> &Tc, const double R_gas, const std::optional<std::vector<std::vector<double>>> & kmat = std::nullopt) : a0(a0), bi(bi), c1(c1), Tc(Tc), R_gas(R_gas), kmat(kmat) {
132 switch (flag) {
133 case cubic_flag::PR:
134 { delta_1 = 1 + sqrt(2.0); delta_2 = 1 - sqrt(2.0); break; }
135 case cubic_flag::SRK:
136 { delta_1 = 1; delta_2 = 0; break; }
137 default:
138 throw std::invalid_argument("Bad cubic flag");
139 }
140 };
141
142 std::size_t size() const {return a0.size(); }
143
144 template<typename VecType>
145 auto R(const VecType& /*molefrac*/) const { return R_gas; }
146
147 template<typename TType>
148 auto get_ai(TType T, int i) const {
149 return forceeval(a0[i] * POW2(1.0 + c1[i]*(1.0 - sqrt(T / Tc[i]))));
150 }
151
152 template<typename TType, typename VecType>
153 auto get_ab(const TType T, const VecType& molefrac) const {
154 using return_type = std::common_type_t<decltype(T), decltype(molefrac[0])>;
155 return_type asummer = 0.0, bsummer = 0.0;
156 for (auto i = 0U; i < molefrac.size(); ++i) {
157 bsummer += molefrac[i] * bi[i];
158 auto ai = get_ai(T, i);
159 for (auto j = 0U; j < molefrac.size(); ++j) {
160 auto aj = get_ai(T, j);
161 double kij = (kmat) ? kmat.value()[i][j] : 0.0;
162 auto a_ij = (1.0 - kij) * sqrt(ai * aj);
163 asummer += molefrac[i] * molefrac[j] * a_ij;
164 }
165 }
166 return std::make_tuple(asummer, bsummer);
167 }
168
169 template<typename TType, typename RhoType, typename VecType>
170 auto alphar(const TType T, const RhoType rhomolar, const VecType& molefrac) const {
171 auto [a_cubic, b_cubic] = get_ab(T, molefrac);
172 return forceeval(
173 -log(1.0 - b_cubic * rhomolar) // repulsive part
174 -a_cubic/R_gas/T*log((delta_1*b_cubic*rhomolar + 1.0) / (delta_2*b_cubic*rhomolar + 1.0)) / b_cubic / (delta_1 - delta_2) // attractive part
175 );
176 }
177};
178
182private:
183 const std::vector<association_classes> classes;
184 const radial_dist dist;
185 const std::valarray<double> epsABi, betaABi, bi;
186 const std::vector<int> N_sites;
187 const double R_gas;
188
189 auto get_N_sites(const std::vector<association_classes> &the_classes) {
190 std::vector<int> N_sites_out;
191 auto get_N = [](auto cl) {
192 switch (cl) {
193 case association_classes::a1A: return 1;
194 case association_classes::a2B: return 2;
195 case association_classes::a3B: return 3;
196 case association_classes::a4C: return 4;
197 default: throw std::invalid_argument("Bad association class");
198 }
199 };
200 for (auto cl : the_classes) {
201 N_sites_out.push_back(get_N(cl));
202 }
203 return N_sites_out;
204 }
205
206public:
207 CPAAssociation(const std::vector<association_classes>& classes, const radial_dist dist, const std::valarray<double> &epsABi, const std::valarray<double> &betaABi, const std::valarray<double> &bi, double R_gas)
208 : classes(classes), dist(dist), epsABi(epsABi), betaABi(betaABi), bi(bi), N_sites(get_N_sites(classes)), R_gas(R_gas) {};
209
210 nlohmann::json get_assoc_calcs(double T, double rhomolar, const Eigen::ArrayXd& mole_fractions) const{
211
212 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;};
213
214 // Calculate b of the mixture
215 auto b_cubic = (Eigen::Map<const Eigen::ArrayXd>(&bi[0], bi.size())*mole_fractions).sum();
216
217 // Calculate the fraction of sites not bonded with other active sites
218 auto RT = forceeval(R_gas * T); // R times T
219 auto XA = XA_calc_pure(N_sites[0], classes[0], dist, epsABi[0], betaABi[0], b_cubic, RT, rhomolar, mole_fractions);
220
221 return {
222 {"b_mix", b_cubic},
223 {"X_A", fromArrayX(XA)},
224 {"note", "X_A is the fraction of non-bonded sites for each site type"}
225 };
226 }
227
228 template<typename TType, typename RhoType, typename VecType>
229 auto alphar(const TType& T, const RhoType& rhomolar, const VecType& molefrac) const {
230 // Calculate b of the mixture
231 auto b_cubic = (Eigen::Map<const Eigen::ArrayXd>(&bi[0], bi.size())*molefrac).sum();
232
233 // Calculate the fraction of sites not bonded with other active sites
234 auto RT = forceeval(R_gas * T); // R times T
235 auto XA = XA_calc_pure(N_sites[0], classes[0], dist, epsABi[0], betaABi[0], b_cubic, RT, rhomolar, molefrac);
236
237 using return_type = std::common_type_t<decltype(T), decltype(rhomolar), decltype(molefrac[0])>;
238 return_type alpha_r_asso = 0.0;
239 auto i = 0;
240 for (auto xi : molefrac){ // loop over all components
241 auto XAi = XA.col(i);
242 alpha_r_asso += forceeval(xi * (log(XAi) - XAi / 2).sum());
243 alpha_r_asso += xi*static_cast<double>(N_sites[i])/2;
244 i++;
245 }
246 return forceeval(alpha_r_asso);
247 }
248};
249
250template <typename Cubic, typename Assoc>
251class CPAEOS {
252public:
253 const Cubic cubic;
254 const Assoc assoc;
255
256 template<class VecType>
257 auto R(const VecType& molefrac) const {
258 return cubic.R(molefrac);
259 }
260
261 CPAEOS(Cubic &&cubic, Assoc &&assoc) : cubic(cubic), assoc(assoc) {
262 }
263
266 template<typename TType, typename RhoType, typename VecType>
267 auto alphar(const TType& T, const RhoType& rhomolar, const VecType& molefrac) const {
268 if (static_cast<std::size_t>(molefrac.size()) != cubic.size()){
269 throw teqp::InvalidArgument("Mole fraction size is not correct; should be " + std::to_string(cubic.size()));
270 }
271
272 // Calculate the contribution to alphar from the conventional cubic EOS
273 auto alpha_r_cubic = cubic.alphar(T, rhomolar, molefrac);
274
275 // Calculate the contribution to alphar from association
276 auto alpha_r_assoc = assoc.alphar(T, rhomolar, molefrac);
277
278 return forceeval(alpha_r_cubic + alpha_r_assoc);
279 }
280};
281
283 using vartype = std::variant<CPAAssociation, association::Association>;
285
287
288 template<typename TType, typename RhoType, typename MoleFracsType>
289 auto alphar(const TType& T, const RhoType& rhomolar, const MoleFracsType& molefracs) const{
290 return std::visit([&](auto& h){ return h.alphar(T, rhomolar, molefracs); }, holder);
291 }
292
293 auto get_assoc_calcs(double T, double rhomolar, const Eigen::ArrayXd& mole_fractions) const {
294 return std::visit([&](auto& h){ return h.get_assoc_calcs(T, rhomolar, mole_fractions); }, holder);
295 }
296
297};
298
301inline auto CPAfactory(const nlohmann::json &j){
302 auto build_cubic = [](const auto& j) {
303 auto N = j["pures"].size();
304 std::valarray<double> a0i(N), bi(N), c1(N), Tc(N);
305 std::vector<std::vector<double>> kmat;
306 if (j.contains("kmat")){
307 kmat = j.at("kmat");
308 std::string kmaterr = "The kmat is the wrong size. It should be square with dimension " + std::to_string(N);
309 if (kmat.size() != N){
310 throw teqp::InvalidArgument(kmaterr);
311 }
312 else{
313 for (auto& krow: kmat){
314 if(krow.size() != N){
315 throw teqp::InvalidArgument(kmaterr);
316 }
317 }
318 }
319 }
320 else{
321 kmat.resize(N); for (auto i = 0U; i < N; ++i){ kmat[i].resize(N); for (auto j = 0U; j < N; ++j){kmat[i][j] = 0.0;} }
322 }
323
324 std::size_t i = 0;
325 for (auto p : j["pures"]) {
326 a0i[i] = p["a0i / Pa m^6/mol^2"];
327 bi[i] = p["bi / m^3/mol"];
328 c1[i] = p["c1"];
329 Tc[i] = p["Tc / K"];
330 i++;
331 }
332 return CPACubic(get_cubic_flag(j["cubic"]), a0i, bi, c1, Tc, j["R_gas / J/mol/K"], kmat);
333 };
334
335 auto build_assoc_pure = [](const auto& j) -> AssociationVariantWrapper{
336 auto N = j["pures"].size();
337 if (N == 1 && j.at("pures").at(0).contains("class") ){
338 // This is the backwards compatible approach
339 // with the old style of defining the association class {1,2B...}
340 std::vector<association_classes> classes;
341 auto dist = get_radial_dist(j.at("radial_dist"));
342 std::valarray<double> epsABi(N), betaABi(N), bi(N);
343 std::size_t i = 0;
344 for (auto p : j.at("pures")) {
345 epsABi[i] = p.at("epsABi / J/mol");
346 betaABi[i] = p.at("betaABi");
347 bi[i] = p.at("bi / m^3/mol");
348 classes.push_back(get_association_classes(p.at("class")));
349 i++;
350 }
351 return AssociationVariantWrapper{CPAAssociation(classes, dist, epsABi, betaABi, bi, j["R_gas / J/mol/K"])};
352 }
353 else{
354 // This branch uses the new code
355 Eigen::ArrayXd b_m3mol(N), beta(N), epsilon_Jmol(N);
357 opt.radial_dist = get_radial_dist(j.at("radial_dist"));
358 if (j.contains("options")){
359 opt = j.at("options"); // Pulls in the options that are POD types
360 }
361 // Copy over the self-association mask
362 if (j.contains("/options/self_association_mask"_json_pointer)){
363 opt.self_association_mask = j.at("/options/self_association_mask"_json_pointer).template get<std::vector<bool>>();
364 }
365
366 std::vector<std::vector<std::string>> molecule_sites;
367 std::size_t i = 0;
368 std::set<std::string> unique_site_types;
369 for (auto p : j["pures"]) {
370 epsilon_Jmol[i] = p.at("epsABi / J/mol");
371 beta[i] = p.at("betaABi");
372 b_m3mol[i] = p.at("bi / m^3/mol");
373 molecule_sites.push_back(p.at("sites"));
374 for (auto & s : molecule_sites.back()){
375 unique_site_types.insert(s);
376 }
377 i++;
378 }
379 if (j.contains("options") && j.at("options").contains("interaction_partners")){
380 opt.interaction_partners = j.at("options").at("interaction_partners");
381 for (auto [k,partners] : opt.interaction_partners){
382 if (unique_site_types.count(k) == 0){
383 throw teqp::InvalidArgument("Site is invalid in interaction_partners: " + k);
384 }
385 for (auto& partner : partners){
386 if (unique_site_types.count(partner) == 0){
387 throw teqp::InvalidArgument("Partner " + partner + " is invalid for site " + k);
388 }
389 }
390 }
391 }
392 else{
393 // Every site type is assumed to interact with every other site type, except for itself
394 for (auto& site1 : unique_site_types){
395 std::vector<std::string> partners;
396 for (auto& site2: unique_site_types){
397 if (site1 != site2){
398 partners.push_back(site2);
399 }
400 }
401 opt.interaction_partners[site1] = partners;
402 }
403 }
404
405 return AssociationVariantWrapper{association::Association(b_m3mol, beta, epsilon_Jmol, molecule_sites, opt)};
406 }
407 };
408 return CPAEOS(build_cubic(j), build_assoc_pure( j));
409}
410
411}; /* namespace CPA */
412
413}; // namespace teqp
CPAAssociation(const std::vector< association_classes > &classes, const radial_dist dist, const std::valarray< double > &epsABi, const std::valarray< double > &betaABi, const std::valarray< double > &bi, double R_gas)
Definition CPA.hpp:207
auto alphar(const TType &T, const RhoType &rhomolar, const VecType &molefrac) const
Definition CPA.hpp:229
nlohmann::json get_assoc_calcs(double T, double rhomolar, const Eigen::ArrayXd &mole_fractions) const
Definition CPA.hpp:210
CPACubic(cubic_flag flag, const std::valarray< double > &a0, const std::valarray< double > &bi, const std::valarray< double > &c1, const std::valarray< double > &Tc, const double R_gas, const std::optional< std::vector< std::vector< double > > > &kmat=std::nullopt)
Definition CPA.hpp:131
auto alphar(const TType T, const RhoType rhomolar, const VecType &molefrac) const
Definition CPA.hpp:170
auto get_ai(TType T, int i) const
Definition CPA.hpp:148
std::size_t size() const
Definition CPA.hpp:142
auto R(const VecType &) const
Definition CPA.hpp:145
auto get_ab(const TType T, const VecType &molefrac) const
Definition CPA.hpp:153
auto alphar(const TType &T, const RhoType &rhomolar, const VecType &molefrac) const
Definition CPA.hpp:267
const Cubic cubic
Definition CPA.hpp:253
auto R(const VecType &molefrac) const
Definition CPA.hpp:257
CPAEOS(Cubic &&cubic, Assoc &&assoc)
Definition CPA.hpp:261
const Assoc assoc
Definition CPA.hpp:254
#define X(f)
auto get_DeltaAB_pure(radial_dist dist, double epsABi, double betaABi, BType b_cubic, TType RT, RhoType rhomolar, const VecType &)
Function that calculates the association binding strength between site A of molecule i and site B on ...
Definition CPA.hpp:27
auto XA_calc_pure(int N_sites, association_classes scheme, radial_dist dist, double epsABi, double betaABi, const BType b_cubic, const TType RT, const RhoType rhomolar, const VecType &molefrac)
Definition CPA.hpp:68
auto POW2(X x)
Definition CPA.hpp:17
auto POW3(X x)
Definition CPA.hpp:18
auto CPAfactory(const nlohmann::json &j)
Definition CPA.hpp:301
auto get_cubic_flag(const std::string &s)
Definition CPA.hpp:115
auto get_association_classes(const std::string &s)
auto get_radial_dist(const std::string &s)
auto forceeval(T &&expr)
Definition types.hpp:52
std::variant< CPAAssociation, association::Association > vartype
Definition CPA.hpp:283
AssociationVariantWrapper(const vartype &holder)
Definition CPA.hpp:286
auto get_assoc_calcs(double T, double rhomolar, const Eigen::ArrayXd &mole_fractions) const
Definition CPA.hpp:293
auto alphar(const TType &T, const RhoType &rhomolar, const MoleFracsType &molefracs) const
Definition CPA.hpp:289
association::radial_dists radial_dist
std::map< std::string, std::vector< std::string > > interaction_partners
std::vector< bool > self_association_mask