teqp 0.22.0
Loading...
Searching...
No Matches
cubics.hpp
Go to the documentation of this file.
1#pragma once
2
3/*
4Implementations of the canonical cubic equations of state
5*/
6
7#include <vector>
8#include <variant>
9#include <valarray>
10#include <optional>
11
12#include "teqp/types.hpp"
13#include "teqp/constants.hpp"
14#include "teqp/exceptions.hpp"
16#include "teqp/json_tools.hpp"
18
19#include "nlohmann/json.hpp"
22
23#include <Eigen/Dense>
24
25namespace teqp {
26
30template<typename NumType>
32private:
33 NumType Tci,
34 mi;
35public:
36 BasicAlphaFunction(NumType Tci, NumType mi) : Tci(Tci), mi(mi) {};
37
38 template<typename TType>
39 auto operator () (const TType& T) const {
40 return forceeval(pow2(forceeval(1.0 + mi * (1.0 - sqrt(T / Tci)))));
41 }
42};
43
50template<typename NumType>
52private:
53 NumType Tci;
54 Eigen::Array3d c;
55public:
56 TwuAlphaFunction(NumType Tci, const Eigen::Array3d &c) : Tci(Tci), c(c) {
57 if (c.size()!= 3){
58 throw teqp::InvalidArgument("coefficients c for Twu alpha function must have length 3");
59 }
60 };
61 template<typename TType>
62 auto operator () (const TType& T) const {
63 return forceeval(pow(T/Tci,c[2]*(c[1]-1))*exp(c[0]*(1.0-pow(T/Tci, c[1]*c[2]))));
64 }
65};
66
78template<typename NumType>
80private:
81 NumType Tci;
82 Eigen::Array3d c;
83public:
84 MathiasCopemanAlphaFunction(NumType Tci, const Eigen::Array3d &c) : Tci(Tci), c(c) {
85 if (c.size()!= 3){
86 throw teqp::InvalidArgument("coefficients c for Mathias-Copeman alpha function must have length 3");
87 }
88 };
89 template<typename TType>
90 auto operator () (const TType& T) const {
91 auto x = 1.0 - sqrt(T/Tci);
92 auto paren = 1.0 + c[0]*x + c[1]*x*x + c[2]*x*x*x;
93 return forceeval(paren*paren);
94 }
95};
96
98
99template<typename TC>
100auto build_alpha_functions(const TC& Tc_K, const nlohmann::json& jalphas){
101 std::vector<AlphaFunctionOptions> alphas;
102 std::size_t i = 0;
103 if (jalphas.size() != Tc_K.size()){
104 throw teqp::InvalidArgument("alpha [size "+std::to_string(jalphas.size())+"] must be the same size as components [size "+std::to_string(Tc_K.size())+"]");
105 }
106 for (auto alpha : jalphas){
107 std::string type = alpha.at("type");
108 if (type == "Twu"){
109 std::valarray<double> c_ = alpha.at("c");
110 Eigen::Array3d c = Eigen::Map<Eigen::Array3d>(&(c_[0]), c_.size());
111 alphas.emplace_back(TwuAlphaFunction(Tc_K[i], c));
112 }
113 else if (type == "Mathias-Copeman"){
114 std::valarray<double> c_ = alpha.at("c");
115 Eigen::Array3d c = Eigen::Map<Eigen::Array3d>(&(c_[0]), c_.size());
116 alphas.emplace_back(MathiasCopemanAlphaFunction(Tc_K[i], c));
117 }
118 else if (type == "PR78"){
119 double acentric = alpha.at("acentric");
120 double mi = 0.0;
121 if (acentric < 0.491) {
122 mi = 0.37464 + 1.54226*acentric - 0.26992*pow2(acentric);
123 }
124 else {
125 mi = 0.379642 + 1.48503*acentric -0.164423*pow2(acentric) + 0.016666*pow3(acentric);
126 }
127 alphas.emplace_back( BasicAlphaFunction(Tc_K[i], mi));
128 }
129 else{
130 throw teqp::InvalidArgument("alpha type is not understood: "+type);
131 }
132 i++;
133 }
134 return alphas;
135};
136
137template <typename NumType, typename AlphaFunctions>
139protected:
140 std::valarray<NumType> ai, bi;
141 const NumType Delta1, Delta2, OmegaA, OmegaB;
143 const AlphaFunctions alphas;
144 Eigen::ArrayXXd kmat;
145
146 nlohmann::json meta;
147 const double m_R_JmolK;
148
149 template<typename TType, typename IndexType>
150 auto get_ai(TType /*T*/, IndexType i) const { return ai[i]; }
151
152 template<typename TType, typename IndexType>
153 auto get_bi(TType /*T*/, IndexType i) const { return bi[i]; }
154
155 template<typename IndexType>
156 void check_kmat(IndexType N) {
157 if (kmat.cols() != kmat.rows()) {
158 throw teqp::InvalidArgument("kmat rows [" + std::to_string(kmat.rows()) + "] and columns [" + std::to_string(kmat.cols()) + "] are not identical");
159 }
160 if (kmat.cols() == 0) {
161 kmat.resize(N, N); kmat.setZero();
162 }
163 else if (static_cast<std::size_t>(kmat.cols()) != N) {
164 throw teqp::InvalidArgument("kmat needs to be a square matrix the same size as the number of components [" + std::to_string(N) + "]");
165 }
166 }
167
168public:
169 GenericCubic(NumType Delta1, NumType Delta2, NumType OmegaA, NumType OmegaB, int superanc_index, const std::valarray<NumType>& Tc_K, const std::valarray<NumType>& pc_Pa, const AlphaFunctions& alphas, const Eigen::ArrayXXd& kmat, const double R_JmolK)
171 {
172 ai.resize(Tc_K.size());
173 bi.resize(Tc_K.size());
174 for (auto i = 0U; i < Tc_K.size(); ++i) {
175 ai[i] = OmegaA * pow2(m_R_JmolK * Tc_K[i]) / pc_Pa[i];
176 bi[i] = OmegaB * m_R_JmolK * Tc_K[i] / pc_Pa[i];
177 }
178 check_kmat(ai.size());
179 };
180
181 void set_meta(const nlohmann::json& j) { meta = j; }
182 auto get_meta() const { return meta; }
183 auto get_kmat() const { return kmat; }
184
189 auto superanc_rhoLV(double T, std::optional<std::size_t> ifluid = std::nullopt) const {
190
191 std::valarray<double> molefracs(ai.size()); molefracs = 1.0;
192
193 // If more than one component, must provide the ifluid argument
194 if(ai.size() > 1){
195 if (!ifluid){
196 throw teqp::InvalidArgument("For mixtures, the argument ifluid must be provided");
197 }
198 if (ifluid.value() > ai.size()-1){
199 throw teqp::InvalidArgument("ifluid must be less than "+std::to_string(ai.size()));
200 }
201 molefracs = 0.0;
202 molefracs[ifluid.value()] = 1.0;
203 }
204
205 auto b = get_b(T, molefracs);
206 auto a = get_a(T, molefracs);
207 auto Ttilde = R(molefracs)*T*b/a;
208 return std::make_tuple(
209 CubicSuperAncillary::supercubic(superanc_index, CubicSuperAncillary::RHOL_CODE, Ttilde)/b,
210 CubicSuperAncillary::supercubic(superanc_index, CubicSuperAncillary::RHOV_CODE, Ttilde)/b
211 );
212 }
213
214 template<class VecType>
215 auto R(const VecType& /*molefrac*/) const {
216 return m_R_JmolK;
217 }
218
219 template<typename TType, typename CompType>
220 auto get_a(TType T, const CompType& molefracs) const {
221 std::common_type_t<TType, decltype(molefracs[0])> a_ = 0.0;
222 for (auto i = 0U; i < molefracs.size(); ++i) {
223 auto alphai = forceeval(std::visit([&](auto& t) { return t(T); }, alphas[i]));
224 auto ai_ = forceeval(this->ai[i] * alphai);
225 for (auto j = 0U; j < molefracs.size(); ++j) {
226 auto alphaj = forceeval(std::visit([&](auto& t) { return t(T); }, alphas[j]));
227 auto aj_ = this->ai[j] * alphaj;
228 auto aij = forceeval((1 - kmat(i,j)) * sqrt(ai_ * aj_));
229 a_ = a_ + molefracs[i] * molefracs[j] * aij;
230 }
231 }
232 return forceeval(a_);
233 }
234
235 template<typename TType, typename CompType>
236 auto get_b(TType /*T*/, const CompType& molefracs) const {
237 std::common_type_t<TType, decltype(molefracs[0])> b_ = 0.0;
238 for (auto i = 0U; i < molefracs.size(); ++i) {
239 b_ = b_ + molefracs[i] * bi[i];
240 }
241 return forceeval(b_);
242 }
243
244 template<typename TType, typename RhoType, typename MoleFracType>
245 auto alphar(const TType& T,
246 const RhoType& rho,
247 const MoleFracType& molefrac) const
248 {
249 if (static_cast<std::size_t>(molefrac.size()) != alphas.size()) {
250 throw std::invalid_argument("Sizes do not match");
251 }
252 auto b = get_b(T, molefrac);
253 auto Psiminus = -log(1.0 - b * rho);
254 auto Psiplus = log((Delta1 * b * rho + 1.0) / (Delta2 * b * rho + 1.0)) / (b * (Delta1 - Delta2));
255 auto val = Psiminus - get_a(T, molefrac) / (m_R_JmolK * T) * Psiplus;
256 return forceeval(val);
257 }
258};
259
260template <typename TCType, typename PCType, typename AcentricType>
261auto canonical_SRK(TCType Tc_K, PCType pc_Pa, AcentricType acentric, const std::optional<Eigen::ArrayXXd>& kmat = std::nullopt, const std::optional<double> R_JmolK = std::nullopt) {
262 double Delta1 = 1;
263 double Delta2 = 0;
264 AcentricType m = 0.48 + 1.574 * acentric - 0.176 * acentric * acentric;
265
266 std::vector<AlphaFunctionOptions> alphas;
267 for (auto i = 0U; i < Tc_K.size(); ++i) {
268 alphas.emplace_back(BasicAlphaFunction(Tc_K[i], m[i]));
269 }
270
271 // See https://doi.org/10.1021/acs.iecr.1c00847
272 double OmegaA = 1.0 / (9.0 * (cbrt(2) - 1));
273 double OmegaB = (cbrt(2) - 1) / 3;
274
275 nlohmann::json meta = {
276 {"Delta1", Delta1},
277 {"Delta2", Delta2},
278 {"OmegaA", OmegaA},
279 {"OmegaB", OmegaB},
280 {"kind", "Soave-Redlich-Kwong"},
281 {"R / J/mol/K", R_JmolK.value_or(constants::R_CODATA2017)}
282 };
283 const std::size_t N = m.size();
284 auto cub = GenericCubic(Delta1, Delta2, OmegaA, OmegaB, CubicSuperAncillary::SRK_CODE, Tc_K, pc_Pa, std::move(alphas), kmat.value_or(Eigen::ArrayXXd::Zero(N,N)), R_JmolK.value_or(constants::R_CODATA2017));
285 cub.set_meta(meta);
286 return cub;
287}
288
290inline auto make_canonicalSRK(const nlohmann::json& spec){
291 std::valarray<double> Tc_K = spec.at("Tcrit / K"), pc_Pa = spec.at("pcrit / Pa"), acentric = spec.at("acentric");
292 Eigen::ArrayXXd kmat(0, 0);
293 if (spec.contains("kmat")){
294 kmat = build_square_matrix(spec.at("kmat"));
295 }
296 return canonical_SRK(Tc_K, pc_Pa, acentric, kmat);
297}
298
299template <typename TCType, typename PCType, typename AcentricType>
300auto canonical_PR(TCType Tc_K, PCType pc_Pa, AcentricType acentric, const std::optional<Eigen::ArrayXXd>& kmat = std::nullopt, const std::optional<double> R_JmolK = std::nullopt) {
301 double Delta1 = 1+sqrt(2.0);
302 double Delta2 = 1-sqrt(2.0);
303 AcentricType m = acentric*0.0;
304 std::vector<AlphaFunctionOptions> alphas;
305 for (auto i = 0U; i < Tc_K.size(); ++i) {
306 if (acentric[i] < 0.491) {
307 m[i] = 0.37464 + 1.54226*acentric[i] - 0.26992*pow2(acentric[i]);
308 }
309 else {
310 m[i] = 0.379642 + 1.48503*acentric[i] -0.164423*pow2(acentric[i]) + 0.016666*pow3(acentric[i]);
311 }
312 alphas.emplace_back(BasicAlphaFunction(Tc_K[i], m[i]));
313 }
314
315 // See https://doi.org/10.1021/acs.iecr.1c00847
316 double OmegaA = 0.45723552892138218938;
317 double OmegaB = 0.077796073903888455972;
318
319 nlohmann::json meta = {
320 {"Delta1", Delta1},
321 {"Delta2", Delta2},
322 {"OmegaA", OmegaA},
323 {"OmegaB", OmegaB},
324 {"kind", "Peng-Robinson"},
325 {"R / J/mol/K", R_JmolK.value_or(constants::R_CODATA2017)}
326 };
327
328 const std::size_t N = m.size();
329 auto cub = GenericCubic(Delta1, Delta2, OmegaA, OmegaB, CubicSuperAncillary::PR_CODE, Tc_K, pc_Pa, std::move(alphas), kmat.value_or(Eigen::ArrayXXd::Zero(N,N)), R_JmolK.value_or(constants::R_CODATA2017));
330 cub.set_meta(meta);
331 return cub;
332}
333
335inline auto make_canonicalPR(const nlohmann::json& spec){
336 std::valarray<double> Tc_K = spec.at("Tcrit / K"), pc_Pa = spec.at("pcrit / Pa"), acentric = spec.at("acentric");
337 Eigen::ArrayXXd kmat(0, 0);
338 if (spec.contains("kmat")){
339 kmat = build_square_matrix(spec.at("kmat"));
340 }
341 return canonical_PR(Tc_K, pc_Pa, acentric, kmat);
342}
343
345inline auto make_generalizedcubic(const nlohmann::json& spec){
346 // Tc, pc, and acentric factor must always be provided
347 std::valarray<double> Tc_K = spec.at("Tcrit / K"),
348 pc_Pa = spec.at("pcrit / Pa"),
349 acentric = spec.at("acentric");
350
351 // If kmat is provided, then collect it
352 std::optional<Eigen::ArrayXXd> kmat;
353 if (spec.contains("kmat")){
354 kmat = build_square_matrix(spec.at("kmat"));
355 }
356 std::optional<double> R_JmolK;
357 if (spec.contains("R / J/mol/K")){
358 R_JmolK = spec.at("R / J/mol/K");
359 }
360
361 int superanc_code = CubicSuperAncillary::UNKNOWN_CODE;
362
363 // Build the list of alpha functions, one per component
364 std::vector<AlphaFunctionOptions> alphas;
365
366 double Delta1, Delta2, OmegaA, OmegaB;
367 std::string kind = "custom";
368
369 if (spec.at("type") == "PR" ){
370 Delta1 = 1+sqrt(2.0);
371 Delta2 = 1-sqrt(2.0);
372 // See https://doi.org/10.1021/acs.iecr.1c00847
373 OmegaA = 0.45723552892138218938;
374 OmegaB = 0.077796073903888455972;
375 superanc_code = CubicSuperAncillary::PR_CODE;
376 kind = "Peng-Robinson";
377
378 if (!spec.contains("alpha")){
379 for (auto i = 0U; i < Tc_K.size(); ++i) {
380 double mi;
381 if (acentric[i] < 0.491) {
382 mi = 0.37464 + 1.54226*acentric[i] - 0.26992*pow2(acentric[i]);
383 }
384 else {
385 mi = 0.379642 + 1.48503*acentric[i] -0.164423*pow2(acentric[i]) + 0.016666*pow3(acentric[i]);
386 }
387 alphas.emplace_back(BasicAlphaFunction(Tc_K[i], mi));
388 }
389 }
390 else{
391 if (!spec["alpha"].is_array()){
392 throw teqp::InvalidArgument("alpha must be array of objects");
393 }
394 alphas = build_alpha_functions(Tc_K, spec.at("alpha"));
395 }
396 }
397 else if (spec.at("type") == "SRK"){
398 Delta1 = 1;
399 Delta2 = 0;
400 if (!spec.contains("alpha")){
401 for (auto i = 0U; i < Tc_K.size(); ++i) {
402 double mi = 0.48 + 1.574 * acentric[i] - 0.176 * acentric[i] * acentric[i];
403 alphas.emplace_back(BasicAlphaFunction(Tc_K[i], mi));
404 }
405 }
406 else{
407 if (!spec["alpha"].is_array()){
408 throw teqp::InvalidArgument("alpha must be array of objects");
409 }
410 alphas = build_alpha_functions(Tc_K, spec.at("alpha"));
411 }
412 // See https://doi.org/10.1021/acs.iecr.1c00847
413 OmegaA = 1.0 / (9.0 * (cbrt(2) - 1));
414 OmegaB = (cbrt(2) - 1) / 3;
415 superanc_code = CubicSuperAncillary::SRK_CODE;
416 kind = "Soave-Redlich-Kwong";
417 }
418 else{
419 // Generalized handling of generic cubics (not yet implemented)
420 throw teqp::InvalidArgument("Generic cubic EOS are not yet supported (open an issue on github if you want this)");
421 }
422
423 const std::size_t N = Tc_K.size();
424 nlohmann::json meta = {
425 {"Delta1", Delta1},
426 {"Delta2", Delta2},
427 {"OmegaA", OmegaA},
428 {"OmegaB", OmegaB},
429 {"kind", kind},
430 {"R / J/mol/K", R_JmolK.value_or(constants::R_CODATA2017)}
431 };
432 if (spec.contains("alpha")){
433 meta["alpha"] = spec.at("alpha");
434 }
435
436 auto cub = GenericCubic(Delta1, Delta2, OmegaA, OmegaB, superanc_code, Tc_K, pc_Pa, std::move(alphas), kmat.value_or(Eigen::ArrayXXd::Zero(N,N)), R_JmolK.value_or(constants::R_CODATA2017));
437 cub.set_meta(meta);
438 return cub;
439}
440
442
447})
448
449struct AdvancedPRaEOptions{
451 double s = 2.0;
452 double CEoS = -sqrt(2.0)/2.0*log(1.0 + sqrt(2.0));
453 double R_JmolK = constants::R_CODATA2017;
454};
455
456inline void from_json(const json& j, AdvancedPRaEOptions& o) {
457 j.at("brule").get_to(o.brule);
458 j.at("s").get_to(o.s);
459 j.at("CEoS").get_to(o.CEoS);
460 if (j.contains("R / J/mol/K")){
461 o.R_JmolK = j.at("R / J/mol/K");
462 }
463}
464
469template <typename NumType, typename AlphaFunctions = std::vector<AlphaFunctionOptions>>
471public:
472 // Hard-coded values for Peng-Robinson
473 const NumType Delta1 = 1+sqrt(2.0);
474 const NumType Delta2 = 1-sqrt(2.0);
475 // See https://doi.org/10.1021/acs.iecr.1c00847
476 const NumType OmegaA = 0.45723552892138218938;
477 const NumType OmegaB = 0.077796073903888455972;
479
480
481protected:
482
483 std::valarray<NumType> Tc_K, pc_Pa;
484
485 std::valarray<NumType> ai, bi;
486
487 const AlphaFunctions alphas;
489 Eigen::ArrayXXd lmat;
490
492 const double s;
493 const double CEoS;
494 const double R_JmolK;
495
496 nlohmann::json meta;
497
498 template<typename TType, typename IndexType>
499 auto get_ai(TType& T, IndexType i) const {
500 auto alphai = std::visit([&](auto& t) { return t(T); }, alphas[i]);
501 return forceeval(ai[i]*alphai);
502 }
503
504 template<typename TType, typename IndexType>
505 auto get_bi(TType& /*T*/, IndexType i) const { return bi[i]; }
506
507 template<typename IndexType>
508 void check_lmat(IndexType N) {
509 if (lmat.cols() != lmat.rows()) {
510 throw teqp::InvalidArgument("lmat rows [" + std::to_string(lmat.rows()) + "] and columns [" + std::to_string(lmat.cols()) + "] are not identical");
511 }
512 if (lmat.cols() == 0) {
513 lmat.resize(N, N); lmat.setZero();
514 }
515 else if (lmat.cols() != static_cast<Eigen::Index>(N)) {
516 throw teqp::InvalidArgument("lmat needs to be a square matrix the same size as the number of components [" + std::to_string(N) + "]");
517 }
518 }
519
520public:
521 AdvancedPRaEres(const std::valarray<NumType>& Tc_K, const std::valarray<NumType>& pc_Pa, const AlphaFunctions& alphas, const ResidualHelmholtzOverRTVariant& ares, const Eigen::ArrayXXd& lmat, const AdvancedPRaEOptions& options = {})
522 : Tc_K(Tc_K), pc_Pa(pc_Pa), alphas(alphas), ares(ares), lmat(lmat), brule(options.brule), s(options.s), CEoS(options.CEoS), R_JmolK(options.R_JmolK)
523 {
524 ai.resize(Tc_K.size());
525 bi.resize(Tc_K.size());
526 for (auto i = 0U; i < Tc_K.size(); ++i) {
527 ai[i] = OmegaA * pow2(R_JmolK * Tc_K[i]) / pc_Pa[i];
528 bi[i] = OmegaB * R_JmolK * Tc_K[i] / pc_Pa[i];
529 }
530 check_lmat(ai.size());
531 };
532
533 void set_meta(const nlohmann::json& j) { meta = j; }
534 auto get_meta() const { return meta; }
535 auto get_lmat() const { return lmat; }
536 auto get_Tc_K() const { return Tc_K; }
537 auto get_pc_Pa() const { return pc_Pa; }
538
539 static double get_bi(double Tc_K, double pc_Pa){
540 const NumType OmegaB = 0.077796073903888455972;
541 const NumType R = 8.31446261815324;
542 return OmegaB*R*Tc_K/pc_Pa;
543 }
544
549 auto superanc_rhoLV(double T, std::optional<std::size_t> ifluid = std::nullopt) const {
550
551 std::valarray<double> molefracs(ai.size()); molefracs = 1.0;
552
553 // If more than one component, must provide the ifluid argument
554 if(ai.size() > 1){
555 if (!ifluid){
556 throw teqp::InvalidArgument("For mixtures, the argument ifluid must be provided");
557 }
558 if (ifluid.value() > ai.size()-1){
559 throw teqp::InvalidArgument("ifluid must be less than "+std::to_string(ai.size()));
560 }
561 molefracs = 0.0;
562 molefracs[ifluid.value()] = 1.0;
563 }
564
565 auto b = get_b(T, molefracs);
566 auto a = get_am_over_bm(T, molefracs)*b;
567 auto Ttilde = R(molefracs)*T*b/a;
568 return std::make_tuple(
569 CubicSuperAncillary::supercubic(superanc_code, CubicSuperAncillary::RHOL_CODE, Ttilde)/b,
570 CubicSuperAncillary::supercubic(superanc_code, CubicSuperAncillary::RHOV_CODE, Ttilde)/b
571 );
572 }
573
574 template<class VecType>
575 auto R(const VecType& /*molefrac*/) const {
576 return R_JmolK;
577 }
578
579 template<typename TType, typename CompType>
580 auto get_a(TType T, const CompType& molefracs) const {
581 return forceeval(get_am_over_bm(T, molefracs)*get_b(T, molefracs));
582 }
583
584 template<typename TType, typename CompType>
585 auto get_am_over_bm(TType T, const CompType& molefracs) const {
586 auto aEresRT = std::visit([&](auto& aresRTfunc) { return aresRTfunc(T, molefracs); }, ares); // aEres/RT, so a non-dimensional quantity
587 std::common_type_t<TType, decltype(molefracs[0])> summer = aEresRT*R_JmolK*T/CEoS;
588 for (auto i = 0U; i < molefracs.size(); ++i) {
589 summer += molefracs[i]*get_ai(T,i)/get_bi(T,i);
590 }
591 return forceeval(summer);
592 }
593
594 template<typename TType, typename CompType>
595 auto get_b(TType T, const CompType& molefracs) const {
596 std::common_type_t<TType, decltype(molefracs[0])> b_ = 0.0;
597
598 switch (brule){
600 for (auto i = 0U; i < molefracs.size(); ++i) {
601 auto bi_ = get_bi(T, i);
602 for (auto j = 0U; j < molefracs.size(); ++j) {
603 auto bj_ = get_bi(T, j);
604
605 auto bij = (1 - lmat(i,j)) * pow((pow(bi_, 1.0/s) + pow(bj_, 1.0/s))/2.0, s);
606 b_ += molefracs[i] * molefracs[j] * bij;
607 }
608 }
609 break;
611 for (auto i = 0U; i < molefracs.size(); ++i) {
612 b_ += molefracs[i] * get_bi(T, i);
613 }
614 break;
615 default:
616 throw teqp::InvalidArgument("Mixing rule for b is invalid");
617 }
618 return forceeval(b_);
619 }
620
621 template<typename TType, typename RhoType, typename MoleFracType>
622 auto alphar(const TType& T,
623 const RhoType& rho,
624 const MoleFracType& molefrac) const
625 {
626 if (static_cast<std::size_t>(molefrac.size()) != alphas.size()) {
627 throw std::invalid_argument("Sizes do not match");
628 }
629 auto b = get_b(T, molefrac);
630 auto a = get_am_over_bm(T, molefrac)*b;
631 auto Psiminus = -log(1.0 - b * rho);
632 auto Psiplus = log((Delta1 * b * rho + 1.0) / (Delta2 * b * rho + 1.0)) / (b * (Delta1 - Delta2));
633 auto val = Psiminus - a / (R_JmolK * T) * Psiplus;
634 return forceeval(val);
635 }
636};
637
638inline auto make_AdvancedPRaEres(const nlohmann::json& j){
639
640 std::valarray<double> Tc_K = j.at("Tcrit / K");
641 std::valarray<double> pc_Pa = j.at("pcrit / Pa");
642
643 std::vector<AlphaFunctionOptions> alphas = build_alpha_functions(Tc_K, j.at("alphas"));
644
645 auto get_ares_model = [&](const nlohmann::json& armodel) -> ResidualHelmholtzOverRTVariant {
646
647 std::string type = armodel.at("type");
648 if (type == "Wilson"){
649 std::vector<double> b;
650 for (auto i = 0U; i < Tc_K.size(); ++i){
651 b.push_back(teqp::AdvancedPRaEres<double>::get_bi(Tc_K[i], pc_Pa[i]));
652 }
653 auto mWilson = build_square_matrix(armodel.at("m"));
654 auto nWilson = build_square_matrix(armodel.at("n"));
655 return WilsonResidualHelmholtzOverRT<double>(b, mWilson, nWilson);
656 }
657 else{
658 throw teqp::InvalidArgument("bad type of ares model: " + type);
659 }
660 };
661 auto aresmodel = get_ares_model(j.at("aresmodel"));
662
663 AdvancedPRaEOptions options = j.at("options");
664 auto model = teqp::AdvancedPRaEres<double>(Tc_K, pc_Pa, alphas, aresmodel, Eigen::ArrayXXd::Zero(2, 2), options);
665 return model;
666}
667
669
679
680private:
681 const std::vector<double> Tc_K, pc_Pa;
682 const std::vector<AlphaFunctionOptions> alphas;
683 const std::vector<double> As, Bs, cs_m3mol;
684 const Eigen::ArrayXXd kmat, lmat;
685 const double Ru;
686
687 auto build_alphas(const nlohmann::json& j){
688 std::vector<AlphaFunctionOptions> alphas_;
689 std::vector<double> L = j.at("Ls"), M = j.at("Ms"), N = j.at("Ns");
690 if (L.size() != M.size() || M.size() != N.size()){
691 throw teqp::InvalidArgument("L,M,N must all be the same length");
692 }
693 for (auto i = 0U; i < L.size(); ++i){
694 auto coeffs = (Eigen::Array3d() << L[i], M[i], N[i]).finished();
695 alphas_.emplace_back(TwuAlphaFunction(Tc_K[i], coeffs));
696 }
697 return alphas_;
698 }
700 std::vector<double> get_(const nlohmann::json &j, const std::string& k) const { return j.at(k).get<std::vector<double>>(); }
701public:
702
703 QuantumCorrectedPR(const nlohmann::json &j) : Tc_K(get_(j, "Tcrit / K")), pc_Pa(get_(j, "pcrit / Pa")), alphas(build_alphas(j)), As(get_(j, "As")), Bs(get_(j, "Bs")), cs_m3mol(get_(j, "cs / m^3/mol")), kmat(build_square_matrix(j.at("kmat"))), lmat(build_square_matrix(j.at("lmat"))), Ru(j.value("R / J/mol/K", constants::R_CODATA2017)) {}
704
705
706
707 template<class VecType>
708 auto R(const VecType& /*molefrac*/) const {
709 return Ru;
710 }
711 auto get_kmat() const { return kmat; }
712 auto get_lmat() const { return lmat; }
713 auto get_Tc_K() const { return Tc_K; }
714 auto get_pc_Pa() const { return pc_Pa; }
715
716 template<typename TType>
717 auto get_bi(std::size_t i, const TType& T) const {
718 auto beta = POW3(1.0 + As[i]/(T+Bs[i]))/POW3(1.0+As[i]/(Tc_K[i]+Bs[i]));
719 // See https://doi.org/10.1021/acs.iecr.1c00847 for the exact value: OmegaB = 0.077796073903888455972;
720 auto b = 0.07780*Tc_K[i]*Ru/pc_Pa[i];
721 return forceeval(b*beta);
722 }
723
724 template<typename TType>
725 auto get_ai(std::size_t i, const TType& T) const {
726 auto alphai = forceeval(std::visit([&](auto& t) { return t(T); }, alphas[i]));
727 // See https://doi.org/10.1021/acs.iecr.1c00847
728 auto OmegaA = 0.45723552892138218938;
729 auto a = OmegaA*POW2(Tc_K[i]*Ru)/pc_Pa[i];
730 return forceeval(a*alphai);
731 }
732
733 template<typename TType, typename FractionsType>
734 auto get_ab(const TType& T, const FractionsType& z) const{
735 using numtype = std::common_type_t<TType, decltype(z[0])>;
736 numtype b = 0.0;
737 numtype a = 0.0;
738 std::size_t N = alphas.size();
739 for (auto i = 0U; i < N; ++i){
740 auto bi = get_bi(i, T);
741 auto ai = get_ai(i, T);
742 for (auto j = 0U; j < N; ++j){
743 auto bj = get_bi(j, T);
744 auto aj = get_ai(j, T);
745 b += z[i]*z[j]*(bi + bj)/2.0*(1.0 - lmat(i,j));
746 a += z[i]*z[j]*sqrt(ai*aj)*(1.0 - kmat(i,j));
747 }
748 }
749 return std::make_tuple(a, b);
750 }
751 template<typename TType, typename FractionsType>
752 auto get_c(const TType& /*T*/, const FractionsType& z) const{
753 using numtype = std::common_type_t<TType, decltype(z[0])>;
754 numtype c = 0.0;
755 std::size_t N = alphas.size();
756 for (auto i = 0U; i < N; ++i){
757 c += z[i]*cs_m3mol[i];
758 }
759 return c;
760 }
761
762 template<typename TType, typename RhoType, typename FractionsType>
763 auto alphar(const TType& T, const RhoType& rhoinit, const FractionsType& molefrac) const {
764 if (static_cast<std::size_t>(molefrac.size()) != alphas.size()) {
765 throw std::invalid_argument("Sizes do not match");
766 }
767 // First shift the volume by the volume translation
768 auto c = get_c(T, molefrac);
769 auto rho = 1.0/(1.0/rhoinit + c);
770 auto Delta1 = 1.0 + sqrt(2.0);
771 auto Delta2 = 1.0 - sqrt(2.0);
772 auto [a, b] = get_ab(T, molefrac);
773 auto Psiminus = -log(1.0 - b * rho);
774 auto Psiplus = log((Delta1 * b * rho + 1.0) / (Delta2 * b * rho + 1.0)) / (b * (Delta1 - Delta2));
775 auto val = Psiminus - a / (Ru * T) * Psiplus;
776 return forceeval(val);
777 }
778
784 auto superanc_rhoLV(double T, const std::optional<std::size_t> ifluid = std::nullopt) const {
785 if (Tc_K.size() != 1) {
786 throw std::invalid_argument("function only available for pure species");
787 }
788
789 std::valarray<double> molefracs(Tc_K.size()); molefracs = 1.0;
790 // If more than one component, must provide the ifluid argument
791 if(Tc_K.size() > 1){
792 if (!ifluid){
793 throw teqp::InvalidArgument("For mixtures, the argument ifluid must be provided");
794 }
795 if (ifluid.value() > Tc_K.size()-1){
796 throw teqp::InvalidArgument("ifluid must be less than "+std::to_string(Tc_K.size()));
797 }
798 molefracs = 0.0;
799 molefracs[ifluid.value()] = 1.0;
800 }
801
802 auto [a, b] = get_ab(T, molefracs);
803 auto Ttilde = R(molefracs)*T*b/a;
804 auto superanc_index = CubicSuperAncillary::PR_CODE;
805 return std::make_tuple(
806 CubicSuperAncillary::supercubic(superanc_index, CubicSuperAncillary::RHOL_CODE, Ttilde)/b,
807 CubicSuperAncillary::supercubic(superanc_index, CubicSuperAncillary::RHOV_CODE, Ttilde)/b
808 );
809 }
810};
811
812
825private:
826 const std::vector<double> delta_1, Tc_K, pc_Pa, k;
827 const Eigen::ArrayXXd kmat, lmat;
828 const double Ru;
829 const std::vector<double> a_c, b_c;
830
832 std::vector<double> get_(const nlohmann::json &j, const std::string& key) const { return j.at(key).get<std::vector<double>>(); }
833
835 auto get_yd1(double delta_1_){
836 return std::make_tuple(1 + cbrt(2*(1+delta_1_)) + cbrt(4/(1+delta_1_)), (1+delta_1_*delta_1_)/(1+delta_1_));
837 }
838
839 auto build_ac(){
840 std::vector<double> a_c_(delta_1.size());
841 for (auto i = 0U; i < delta_1.size(); ++i){
842 auto [y, d_1] = get_yd1(delta_1[i]);
843 auto Omega_a = (3*y*y + 3*y*d_1 + d_1*d_1 + d_1 - 1.0)/pow(3.0*y + d_1 - 1.0, 2);
844 a_c_[i] = Omega_a*pow(Ru*Tc_K[i], 2)/pc_Pa[i];
845 }
846 return a_c_;
847 }
848 auto build_bc(){
849 std::vector<double> b_c_(delta_1.size());
850 for (auto i = 0U; i < delta_1.size(); ++i){
851 auto [y, d_1] = get_yd1(delta_1[i]);
852 auto Omega_b = 1.0/(3.0*y + d_1 - 1.0);
853 b_c_[i] = Omega_b*Ru*Tc_K[i]/pc_Pa[i];
854 }
855 return b_c_;
856 }
857public:
858
859 RKPRCismondi2005(const nlohmann::json &j) : delta_1(get_(j, "delta_1")), Tc_K(get_(j, "Tcrit / K")), pc_Pa(get_(j, "pcrit / Pa")), k(get_(j, "k")), kmat(build_square_matrix(j.at("kmat"))), lmat(build_square_matrix(j.at("lmat"))), Ru(j.value("R / J/mol/K", constants::R_CODATA2017)), a_c(build_ac()), b_c(build_bc()) {}
860
861 template<class VecType>
862 auto R(const VecType& /*molefrac*/) const {
863 return Ru;
864 }
865 auto get_delta_1() const { return delta_1; }
866 auto get_Tc_K() const { return Tc_K; }
867 auto get_pc_Pa() const { return pc_Pa; }
868 auto get_k() const { return k; }
869 auto get_kmat() const { return kmat; }
870 auto get_lmat() const { return lmat; }
871
872 template<typename TType>
873 auto get_bi(std::size_t i, const TType& /*T*/) const {
874 return b_c[i];
875 }
876
877 template<typename TType>
878 auto get_ai(std::size_t i, const TType& T) const {
879 return forceeval(a_c[i]*pow(3.0/(2.0+T/Tc_K[i]), k[i]));
880 }
881
882 template<typename TType, typename FractionsType>
883 auto get_ab(const TType& T, const FractionsType& z) const{
884 using numtype = std::common_type_t<TType, decltype(z[0])>;
885 numtype b = 0.0;
886 numtype a = 0.0;
887 std::size_t N = delta_1.size();
888 for (auto i = 0U; i < N; ++i){
889 auto bi = get_bi(i, T);
890 auto ai = get_ai(i, T);
891 for (auto j = 0U; j < N; ++j){
892 auto aj = get_ai(j, T);
893 auto bj = get_bi(j, T);
894
895 a += z[i]*z[j]*sqrt(ai*aj)*(1.0 - kmat(i,j));
896 b += z[i]*z[j]*(bi + bj)/2.0*(1.0 - lmat(i,j));
897
898 }
899 }
900 return std::make_tuple(a, b);
901 }
902
903 template<typename TType, typename RhoType, typename FractionsType>
904 auto alphar(const TType& T, const RhoType& rho, const FractionsType& molefrac) const {
905 if (static_cast<std::size_t>(molefrac.size()) != delta_1.size()) {
906 throw std::invalid_argument("Sizes do not match");
907 }
908
909 // The mixture Delta_1 is a mole-fraction-weighted average of the pure values of delta_1
910 const auto delta_1_view = Eigen::Map<const Eigen::ArrayXd>(&(delta_1[0]), delta_1.size());
911 const decltype(molefrac[0]) Delta1 = (molefrac*delta_1_view).eval().sum();
912
913 auto Delta2 = (1.0-Delta1)/(1.0+Delta1);
914 auto [a, b] = get_ab(T, molefrac);
915
916 auto Psiminus = -log(1.0 - b*rho);
917 auto Psiplus = log((Delta1 * b * rho + 1.0) / (Delta2 * b * rho + 1.0)) / (b * (Delta1 - Delta2));
918 auto val = Psiminus - a/(this->Ru*T)*Psiplus;
919 return forceeval(val);
920 }
921};
923
924
925
926}; // namespace teqp
const double R_JmolK
Definition cubics.hpp:494
auto get_a(TType T, const CompType &molefracs) const
Definition cubics.hpp:580
auto get_b(TType T, const CompType &molefracs) const
Definition cubics.hpp:595
AdvancedPRaEres(const std::valarray< NumType > &Tc_K, const std::valarray< NumType > &pc_Pa, const AlphaFunctions &alphas, const ResidualHelmholtzOverRTVariant &ares, const Eigen::ArrayXXd &lmat, const AdvancedPRaEOptions &options={})
Definition cubics.hpp:521
void check_lmat(IndexType N)
Definition cubics.hpp:508
nlohmann::json meta
Definition cubics.hpp:496
std::valarray< NumType > Tc_K
Definition cubics.hpp:483
Eigen::ArrayXXd lmat
Definition cubics.hpp:489
static double get_bi(double Tc_K, double pc_Pa)
Definition cubics.hpp:539
const ResidualHelmholtzOverRTVariant ares
Definition cubics.hpp:488
auto get_Tc_K() const
Definition cubics.hpp:536
std::valarray< NumType > ai
Definition cubics.hpp:485
auto get_ai(TType &T, IndexType i) const
Definition cubics.hpp:499
const AlphaFunctions alphas
Definition cubics.hpp:487
const AdvancedPRaEMixingRules brule
Definition cubics.hpp:491
const NumType Delta2
Definition cubics.hpp:474
const NumType OmegaB
Definition cubics.hpp:477
void set_meta(const nlohmann::json &j)
Definition cubics.hpp:533
std::valarray< NumType > pc_Pa
Definition cubics.hpp:483
auto get_bi(TType &, IndexType i) const
Definition cubics.hpp:505
std::valarray< NumType > bi
Definition cubics.hpp:485
const double CEoS
Definition cubics.hpp:493
const int superanc_code
Definition cubics.hpp:478
auto R(const VecType &) const
Definition cubics.hpp:575
const NumType Delta1
Definition cubics.hpp:473
auto get_lmat() const
Definition cubics.hpp:535
auto get_pc_Pa() const
Definition cubics.hpp:537
auto superanc_rhoLV(double T, std::optional< std::size_t > ifluid=std::nullopt) const
Definition cubics.hpp:549
auto get_meta() const
Definition cubics.hpp:534
const NumType OmegaA
Definition cubics.hpp:476
auto get_am_over_bm(TType T, const CompType &molefracs) const
Definition cubics.hpp:585
auto alphar(const TType &T, const RhoType &rho, const MoleFracType &molefrac) const
Definition cubics.hpp:622
The standard alpha function used by Peng-Robinson and SRK.
Definition cubics.hpp:31
auto operator()(const TType &T) const
Definition cubics.hpp:39
BasicAlphaFunction(NumType Tci, NumType mi)
Definition cubics.hpp:36
auto get_ai(TType, IndexType i) const
Definition cubics.hpp:150
const double m_R_JmolK
Definition cubics.hpp:147
const NumType OmegaB
Definition cubics.hpp:141
const NumType OmegaA
Definition cubics.hpp:141
GenericCubic(NumType Delta1, NumType Delta2, NumType OmegaA, NumType OmegaB, int superanc_index, const std::valarray< NumType > &Tc_K, const std::valarray< NumType > &pc_Pa, const AlphaFunctions &alphas, const Eigen::ArrayXXd &kmat, const double R_JmolK)
Definition cubics.hpp:169
auto get_b(TType, const CompType &molefracs) const
Definition cubics.hpp:236
const NumType Delta2
Definition cubics.hpp:141
void set_meta(const nlohmann::json &j)
Definition cubics.hpp:181
void check_kmat(IndexType N)
Definition cubics.hpp:156
auto alphar(const TType &T, const RhoType &rho, const MoleFracType &molefrac) const
Definition cubics.hpp:245
auto get_a(TType T, const CompType &molefracs) const
Definition cubics.hpp:220
std::valarray< NumType > ai
Definition cubics.hpp:140
auto superanc_rhoLV(double T, std::optional< std::size_t > ifluid=std::nullopt) const
Definition cubics.hpp:189
const NumType Delta1
Definition cubics.hpp:141
Eigen::ArrayXXd kmat
Definition cubics.hpp:144
auto get_kmat() const
Definition cubics.hpp:183
auto get_bi(TType, IndexType i) const
Definition cubics.hpp:153
const AlphaFunctions alphas
Definition cubics.hpp:143
std::valarray< NumType > bi
Definition cubics.hpp:140
auto get_meta() const
Definition cubics.hpp:182
nlohmann::json meta
Definition cubics.hpp:146
auto R(const VecType &) const
Definition cubics.hpp:215
The Mathias-Copeman alpha function used by Peng-Robinson and SRK.
Definition cubics.hpp:79
auto operator()(const TType &T) const
Definition cubics.hpp:90
MathiasCopemanAlphaFunction(NumType Tci, const Eigen::Array3d &c)
Definition cubics.hpp:84
auto get_ai(std::size_t i, const TType &T) const
Definition cubics.hpp:725
auto get_pc_Pa() const
Definition cubics.hpp:714
auto get_ab(const TType &T, const FractionsType &z) const
Definition cubics.hpp:734
auto alphar(const TType &T, const RhoType &rhoinit, const FractionsType &molefrac) const
Definition cubics.hpp:763
auto get_Tc_K() const
Definition cubics.hpp:713
QuantumCorrectedPR(const nlohmann::json &j)
Definition cubics.hpp:703
auto get_bi(std::size_t i, const TType &T) const
Definition cubics.hpp:717
auto R(const VecType &) const
Definition cubics.hpp:708
auto superanc_rhoLV(double T, const std::optional< std::size_t > ifluid=std::nullopt) const
Definition cubics.hpp:784
auto get_kmat() const
Definition cubics.hpp:711
auto get_lmat() const
Definition cubics.hpp:712
auto get_c(const TType &, const FractionsType &z) const
Definition cubics.hpp:752
RKPRCismondi2005(const nlohmann::json &j)
Definition cubics.hpp:859
auto get_bi(std::size_t i, const TType &) const
Definition cubics.hpp:873
auto get_delta_1() const
Definition cubics.hpp:865
auto R(const VecType &) const
Definition cubics.hpp:862
auto alphar(const TType &T, const RhoType &rho, const FractionsType &molefrac) const
Definition cubics.hpp:904
auto get_k() const
Definition cubics.hpp:868
auto get_ab(const TType &T, const FractionsType &z) const
Definition cubics.hpp:883
auto get_kmat() const
Definition cubics.hpp:869
auto get_Tc_K() const
Definition cubics.hpp:866
auto get_pc_Pa() const
Definition cubics.hpp:867
auto get_ai(std::size_t i, const TType &T) const
Definition cubics.hpp:878
auto get_lmat() const
Definition cubics.hpp:870
The Twu alpha function used by Peng-Robinson and SRK.
Definition cubics.hpp:51
auto operator()(const TType &T) const
Definition cubics.hpp:62
TwuAlphaFunction(NumType Tci, const Eigen::Array3d &c)
Definition cubics.hpp:56
std::variant< NullResidualHelmholtzOverRT< double >, WilsonResidualHelmholtzOverRT< double >, BinaryInvariantResidualHelmholtzOverRT< double >, COSMOSAC::COSMO3 > ResidualHelmholtzOverRTVariant
const double R_CODATA2017
molar gas constant from CODATA 2017: https://doi.org/10.1103/RevModPhys.84.1527
Definition constants.hpp:10
auto POW2(const A &x)
auto build_square_matrix
T pow3(const T &x)
Definition types.hpp:8
AdvancedPRaEMixingRules
Definition cubics.hpp:441
auto make_generalizedcubic(const nlohmann::json &spec)
A JSON-based factory function for the generalized cubic + alpha.
Definition cubics.hpp:345
auto make_AdvancedPRaEres(const nlohmann::json &j)
Definition cubics.hpp:638
void from_json(const json &j, AdvancedPRaEOptions &o)
Definition cubics.hpp:456
decltype(make_AdvancedPRaEres({})) advancedPRaEres_t
Definition cubics.hpp:668
auto POW3(const A &x)
auto canonical_PR(TCType Tc_K, PCType pc_Pa, AcentricType acentric, const std::optional< Eigen::ArrayXXd > &kmat=std::nullopt, const std::optional< double > R_JmolK=std::nullopt)
Definition cubics.hpp:300
auto make_canonicalPR(const nlohmann::json &spec)
A JSON-based factory function for the canonical SRK model.
Definition cubics.hpp:335
decltype(RKPRCismondi2005({})) RKPRCismondi2005_t
Definition cubics.hpp:922
T pow2(const T &x)
Definition types.hpp:7
std::variant< BasicAlphaFunction< double >, TwuAlphaFunction< double >, MathiasCopemanAlphaFunction< double > > AlphaFunctionOptions
Definition cubics.hpp:97
auto make_canonicalSRK(const nlohmann::json &spec)
A JSON-based factory function for the canonical SRK model.
Definition cubics.hpp:290
NLOHMANN_JSON_SERIALIZE_ENUM(AdvancedPRaEMixingRules, { {AdvancedPRaEMixingRules::knotspecified, nullptr}, {AdvancedPRaEMixingRules::kLinear, "Linear"}, {AdvancedPRaEMixingRules::kQuadratic, "Quadratic"}, }) struct AdvancedPRaEOptions
Definition cubics.hpp:443
auto pow(const double &x, const double &e)
Definition types.hpp:195
auto forceeval(T &&expr)
Definition types.hpp:52
auto build_alpha_functions(const TC &Tc_K, const nlohmann::json &jalphas)
Definition cubics.hpp:100
auto canonical_SRK(TCType Tc_K, PCType pc_Pa, AcentricType acentric, const std::optional< Eigen::ArrayXXd > &kmat=std::nullopt, const std::optional< double > R_JmolK=std::nullopt)
Definition cubics.hpp:261