teqp 0.23.1
Loading...
Searching...
No Matches
simple_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"
20
21#include <Eigen/Dense>
22
23namespace teqp {
24
28template<typename NumType>
30private:
31 NumType Tci,
32 mi;
33public:
34 BasicAlphaFunction(NumType Tci, NumType mi) : Tci(Tci), mi(mi) {};
35
36 template<typename TType>
37 auto operator () (const TType& T) const {
38 return forceeval(pow2(forceeval(1.0 + mi * (1.0 - sqrt(T / Tci)))));
39 }
40};
41
48template<typename NumType>
50private:
51 NumType Tci;
52 Eigen::Array3d c;
53public:
54 TwuAlphaFunction(NumType Tci, const Eigen::Array3d &c) : Tci(Tci), c(c) {
55 if (c.size()!= 3){
56 throw teqp::InvalidArgument("coefficients c for Twu alpha function must have length 3");
57 }
58 };
59 template<typename TType>
60 auto operator () (const TType& T) const {
61 return forceeval(pow(T/Tci,c[2]*(c[1]-1))*exp(c[0]*(1.0-pow(T/Tci, c[1]*c[2]))));
62 }
63};
64
76template<typename NumType>
78private:
79 NumType Tci;
80 Eigen::Array3d c;
81public:
82 MathiasCopemanAlphaFunction(NumType Tci, const Eigen::Array3d &c) : Tci(Tci), c(c) {
83 if (c.size()!= 3){
84 throw teqp::InvalidArgument("coefficients c for Mathias-Copeman alpha function must have length 3");
85 }
86 };
87 template<typename TType>
88 auto operator () (const TType& T) const {
89 auto x = 1.0 - sqrt(T/Tci);
90 auto paren = 1.0 + c[0]*x + c[1]*x*x + c[2]*x*x*x;
91 return forceeval(paren*paren);
92 }
93};
94
96
97template<typename TC>
98auto build_alpha_functions(const TC& Tc_K, const nlohmann::json& jalphas){
99 std::vector<AlphaFunctionOptions> alphas;
100 std::size_t i = 0;
101 if (jalphas.size() != Tc_K.size()){
102 throw teqp::InvalidArgument("alpha [size "+std::to_string(jalphas.size())+"] must be the same size as components [size "+std::to_string(Tc_K.size())+"]");
103 }
104 for (auto alpha : jalphas){
105 std::string type = alpha.at("type");
106 if (type == "Twu"){
107 std::valarray<double> c_ = alpha.at("c");
108 Eigen::Array3d c = Eigen::Map<Eigen::Array3d>(&(c_[0]), c_.size());
109 alphas.emplace_back(TwuAlphaFunction(Tc_K[i], c));
110 }
111 else if (type == "Mathias-Copeman"){
112 std::valarray<double> c_ = alpha.at("c");
113 Eigen::Array3d c = Eigen::Map<Eigen::Array3d>(&(c_[0]), c_.size());
114 alphas.emplace_back(MathiasCopemanAlphaFunction(Tc_K[i], c));
115 }
116 else if (type == "PR78"){
117 double acentric = alpha.at("acentric");
118 double mi = 0.0;
119 if (acentric < 0.491) {
120 mi = 0.37464 + 1.54226*acentric - 0.26992*pow2(acentric);
121 }
122 else {
123 mi = 0.379642 + 1.48503*acentric -0.164423*pow2(acentric) + 0.016666*pow3(acentric);
124 }
125 alphas.emplace_back( BasicAlphaFunction(Tc_K[i], mi));
126 }
127 else{
128 throw teqp::InvalidArgument("alpha type is not understood: "+type);
129 }
130 i++;
131 }
132 return alphas;
133};
134
135template <typename NumType, typename AlphaFunctions>
137protected:
138 std::valarray<NumType> ai, bi;
139 const NumType Delta1, Delta2, OmegaA, OmegaB;
141 const AlphaFunctions alphas;
142 Eigen::ArrayXXd kmat;
143
144 nlohmann::json meta;
145 const double m_R_JmolK;
146
147 template<typename TType, typename IndexType>
148 auto get_ai(TType /*T*/, IndexType i) const { return ai[i]; }
149
150 template<typename TType, typename IndexType>
151 auto get_bi(TType /*T*/, IndexType i) const { return bi[i]; }
152
153 template<typename IndexType>
154 void check_kmat(IndexType N) {
155 if (kmat.cols() != kmat.rows()) {
156 throw teqp::InvalidArgument("kmat rows [" + std::to_string(kmat.rows()) + "] and columns [" + std::to_string(kmat.cols()) + "] are not identical");
157 }
158 if (kmat.cols() == 0) {
159 kmat.resize(N, N); kmat.setZero();
160 }
161 else if (static_cast<std::size_t>(kmat.cols()) != N) {
162 throw teqp::InvalidArgument("kmat needs to be a square matrix the same size as the number of components [" + std::to_string(N) + "]");
163 }
164 }
165
166public:
167 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)
169 {
170 ai.resize(Tc_K.size());
171 bi.resize(Tc_K.size());
172 for (auto i = 0U; i < Tc_K.size(); ++i) {
173 ai[i] = OmegaA * pow2(m_R_JmolK * Tc_K[i]) / pc_Pa[i];
174 bi[i] = OmegaB * m_R_JmolK * Tc_K[i] / pc_Pa[i];
175 }
176 check_kmat(ai.size());
177 };
178
179 void set_meta(const nlohmann::json& j) { meta = j; }
180 auto get_meta() const { return meta; }
181 auto get_kmat() const { return kmat; }
182
187 auto superanc_rhoLV(double T, std::optional<std::size_t> ifluid = std::nullopt) const {
188
189 std::valarray<double> molefracs(ai.size()); molefracs = 1.0;
190
191 // If more than one component, must provide the ifluid argument
192 if(ai.size() > 1){
193 if (!ifluid){
194 throw teqp::InvalidArgument("For mixtures, the argument ifluid must be provided");
195 }
196 if (ifluid.value() > ai.size()-1){
197 throw teqp::InvalidArgument("ifluid must be less than "+std::to_string(ai.size()));
198 }
199 molefracs = 0.0;
200 molefracs[ifluid.value()] = 1.0;
201 }
202
203 auto b = get_b(T, molefracs);
204 auto a = get_a(T, molefracs);
205 auto Ttilde = R(molefracs)*T*b/a;
206 return std::make_tuple(
207 CubicSuperAncillary::supercubic(superanc_index, CubicSuperAncillary::RHOL_CODE, Ttilde)/b,
208 CubicSuperAncillary::supercubic(superanc_index, CubicSuperAncillary::RHOV_CODE, Ttilde)/b
209 );
210 }
211
212 template<class VecType>
213 auto R(const VecType& /*molefrac*/) const {
214 return m_R_JmolK;
215 }
216
217 template<typename TType, typename CompType>
218 auto get_a(TType T, const CompType& molefracs) const {
219 std::common_type_t<TType, decltype(molefracs[0])> a_ = 0.0;
220 for (auto i = 0U; i < molefracs.size(); ++i) {
221 auto alphai = forceeval(std::visit([&](auto& t) { return t(T); }, alphas[i]));
222 auto ai_ = forceeval(this->ai[i] * alphai);
223 for (auto j = 0U; j < molefracs.size(); ++j) {
224 auto alphaj = forceeval(std::visit([&](auto& t) { return t(T); }, alphas[j]));
225 auto aj_ = this->ai[j] * alphaj;
226 auto aij = forceeval((1 - kmat(i,j)) * sqrt(ai_ * aj_));
227 a_ = a_ + molefracs[i] * molefracs[j] * aij;
228 }
229 }
230 return forceeval(a_);
231 }
232
233 template<typename TType, typename CompType>
234 auto get_b(TType /*T*/, const CompType& molefracs) const {
235 std::common_type_t<TType, decltype(molefracs[0])> b_ = 0.0;
236 for (auto i = 0U; i < molefracs.size(); ++i) {
237 b_ = b_ + molefracs[i] * bi[i];
238 }
239 return forceeval(b_);
240 }
241
242 template<typename TType, typename RhoType, typename MoleFracType>
243 auto alphar(const TType& T,
244 const RhoType& rho,
245 const MoleFracType& molefrac) const
246 {
247 if (static_cast<std::size_t>(molefrac.size()) != alphas.size()) {
248 throw std::invalid_argument("Sizes do not match");
249 }
250 auto b = get_b(T, molefrac);
251 auto Psiminus = -log(1.0 - b * rho);
252 auto Psiplus = log((Delta1 * b * rho + 1.0) / (Delta2 * b * rho + 1.0)) / (b * (Delta1 - Delta2));
253 auto val = Psiminus - get_a(T, molefrac) / (m_R_JmolK * T) * Psiplus;
254 return forceeval(val);
255 }
256};
257
258template <typename TCType, typename PCType, typename AcentricType>
259auto 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) {
260 double Delta1 = 1;
261 double Delta2 = 0;
262 AcentricType m = 0.48 + 1.574 * acentric - 0.176 * acentric * acentric;
263
264 std::vector<AlphaFunctionOptions> alphas;
265 for (auto i = 0U; i < Tc_K.size(); ++i) {
266 alphas.emplace_back(BasicAlphaFunction(Tc_K[i], m[i]));
267 }
268
269 // See https://doi.org/10.1021/acs.iecr.1c00847
270 double OmegaA = 1.0 / (9.0 * (cbrt(2) - 1));
271 double OmegaB = (cbrt(2) - 1) / 3;
272
273 nlohmann::json meta = {
274 {"Delta1", Delta1},
275 {"Delta2", Delta2},
276 {"OmegaA", OmegaA},
277 {"OmegaB", OmegaB},
278 {"kind", "Soave-Redlich-Kwong"},
279 {"R / J/mol/K", R_JmolK.value_or(constants::R_CODATA2017)}
280 };
281 const std::size_t N = m.size();
282 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));
283 cub.set_meta(meta);
284 return cub;
285}
286
288inline auto make_canonicalSRK(const nlohmann::json& spec){
289 std::valarray<double> Tc_K = spec.at("Tcrit / K"), pc_Pa = spec.at("pcrit / Pa"), acentric = spec.at("acentric");
290 Eigen::ArrayXXd kmat(0, 0);
291 if (spec.contains("kmat")){
292 kmat = build_square_matrix(spec.at("kmat"));
293 }
294 return canonical_SRK(Tc_K, pc_Pa, acentric, kmat);
295}
296
297template <typename TCType, typename PCType, typename AcentricType>
298auto 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) {
299 double Delta1 = 1+sqrt(2.0);
300 double Delta2 = 1-sqrt(2.0);
301 AcentricType m = acentric*0.0;
302 std::vector<AlphaFunctionOptions> alphas;
303 for (auto i = 0U; i < Tc_K.size(); ++i) {
304 if (acentric[i] < 0.491) {
305 m[i] = 0.37464 + 1.54226*acentric[i] - 0.26992*pow2(acentric[i]);
306 }
307 else {
308 m[i] = 0.379642 + 1.48503*acentric[i] -0.164423*pow2(acentric[i]) + 0.016666*pow3(acentric[i]);
309 }
310 alphas.emplace_back(BasicAlphaFunction(Tc_K[i], m[i]));
311 }
312
313 // See https://doi.org/10.1021/acs.iecr.1c00847
314 double OmegaA = 0.45723552892138218938;
315 double OmegaB = 0.077796073903888455972;
316
317 nlohmann::json meta = {
318 {"Delta1", Delta1},
319 {"Delta2", Delta2},
320 {"OmegaA", OmegaA},
321 {"OmegaB", OmegaB},
322 {"kind", "Peng-Robinson"},
323 {"R / J/mol/K", R_JmolK.value_or(constants::R_CODATA2017)}
324 };
325
326 const std::size_t N = m.size();
327 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));
328 cub.set_meta(meta);
329 return cub;
330}
331
333inline auto make_canonicalPR(const nlohmann::json& spec){
334 std::valarray<double> Tc_K = spec.at("Tcrit / K"), pc_Pa = spec.at("pcrit / Pa"), acentric = spec.at("acentric");
335 Eigen::ArrayXXd kmat(0, 0);
336 if (spec.contains("kmat")){
337 kmat = build_square_matrix(spec.at("kmat"));
338 }
339 return canonical_PR(Tc_K, pc_Pa, acentric, kmat);
340}
341
343inline auto make_generalizedcubic(const nlohmann::json& spec){
344 // Tc, pc, and acentric factor must always be provided
345 std::valarray<double> Tc_K = spec.at("Tcrit / K"),
346 pc_Pa = spec.at("pcrit / Pa"),
347 acentric = spec.at("acentric");
348
349 // If kmat is provided, then collect it
350 std::optional<Eigen::ArrayXXd> kmat;
351 if (spec.contains("kmat")){
352 kmat = build_square_matrix(spec.at("kmat"));
353 }
354 std::optional<double> R_JmolK;
355 if (spec.contains("R / J/mol/K")){
356 R_JmolK = spec.at("R / J/mol/K");
357 }
358
359 int superanc_code = CubicSuperAncillary::UNKNOWN_CODE;
360
361 // Build the list of alpha functions, one per component
362 std::vector<AlphaFunctionOptions> alphas;
363
364 double Delta1, Delta2, OmegaA, OmegaB;
365 std::string kind = "custom";
366
367 if (spec.at("type") == "PR" ){
368 Delta1 = 1+sqrt(2.0);
369 Delta2 = 1-sqrt(2.0);
370 // See https://doi.org/10.1021/acs.iecr.1c00847
371 OmegaA = 0.45723552892138218938;
372 OmegaB = 0.077796073903888455972;
373 superanc_code = CubicSuperAncillary::PR_CODE;
374 kind = "Peng-Robinson";
375
376 if (!spec.contains("alpha")){
377 for (auto i = 0U; i < Tc_K.size(); ++i) {
378 double mi;
379 if (acentric[i] < 0.491) {
380 mi = 0.37464 + 1.54226*acentric[i] - 0.26992*pow2(acentric[i]);
381 }
382 else {
383 mi = 0.379642 + 1.48503*acentric[i] -0.164423*pow2(acentric[i]) + 0.016666*pow3(acentric[i]);
384 }
385 alphas.emplace_back(BasicAlphaFunction(Tc_K[i], mi));
386 }
387 }
388 else{
389 if (!spec["alpha"].is_array()){
390 throw teqp::InvalidArgument("alpha must be array of objects");
391 }
392 alphas = build_alpha_functions(Tc_K, spec.at("alpha"));
393 }
394 }
395 else if (spec.at("type") == "SRK"){
396 Delta1 = 1;
397 Delta2 = 0;
398 if (!spec.contains("alpha")){
399 for (auto i = 0U; i < Tc_K.size(); ++i) {
400 double mi = 0.48 + 1.574 * acentric[i] - 0.176 * acentric[i] * acentric[i];
401 alphas.emplace_back(BasicAlphaFunction(Tc_K[i], mi));
402 }
403 }
404 else{
405 if (!spec["alpha"].is_array()){
406 throw teqp::InvalidArgument("alpha must be array of objects");
407 }
408 alphas = build_alpha_functions(Tc_K, spec.at("alpha"));
409 }
410 // See https://doi.org/10.1021/acs.iecr.1c00847
411 OmegaA = 1.0 / (9.0 * (cbrt(2) - 1));
412 OmegaB = (cbrt(2) - 1) / 3;
413 superanc_code = CubicSuperAncillary::SRK_CODE;
414 kind = "Soave-Redlich-Kwong";
415 }
416 else{
417 // Generalized handling of generic cubics (not yet implemented)
418 throw teqp::InvalidArgument("Generic cubic EOS are not yet supported (open an issue on github if you want this)");
419 }
420
421 const std::size_t N = Tc_K.size();
422 nlohmann::json meta = {
423 {"Delta1", Delta1},
424 {"Delta2", Delta2},
425 {"OmegaA", OmegaA},
426 {"OmegaB", OmegaB},
427 {"kind", kind},
428 {"R / J/mol/K", R_JmolK.value_or(constants::R_CODATA2017)}
429 };
430 if (spec.contains("alpha")){
431 meta["alpha"] = spec.at("alpha");
432 }
433
434 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));
435 cub.set_meta(meta);
436 return cub;
437}
438
448
449private:
450 const std::vector<double> Tc_K, pc_Pa;
451 const std::vector<AlphaFunctionOptions> alphas;
452 const std::vector<double> As, Bs, cs_m3mol;
453 const Eigen::ArrayXXd kmat, lmat;
454 const double Ru;
455
456 auto build_alphas(const nlohmann::json& j){
457 std::vector<AlphaFunctionOptions> alphas_;
458 std::vector<double> L = j.at("Ls"), M = j.at("Ms"), N = j.at("Ns");
459 if (L.size() != M.size() || M.size() != N.size()){
460 throw teqp::InvalidArgument("L,M,N must all be the same length");
461 }
462 for (auto i = 0U; i < L.size(); ++i){
463 auto coeffs = (Eigen::Array3d() << L[i], M[i], N[i]).finished();
464 alphas_.emplace_back(TwuAlphaFunction(Tc_K[i], coeffs));
465 }
466 return alphas_;
467 }
469 std::vector<double> get_(const nlohmann::json &j, const std::string& k) const { return j.at(k).get<std::vector<double>>(); }
470public:
471
472 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)) {}
473
474
475
476 template<class VecType>
477 auto R(const VecType& /*molefrac*/) const {
478 return Ru;
479 }
480 auto get_kmat() const { return kmat; }
481 auto get_lmat() const { return lmat; }
482 auto get_Tc_K() const { return Tc_K; }
483 auto get_pc_Pa() const { return pc_Pa; }
484
485 template<typename TType>
486 auto get_bi(std::size_t i, const TType& T) const {
487 auto beta = POW3(1.0 + As[i]/(T+Bs[i]))/POW3(1.0+As[i]/(Tc_K[i]+Bs[i]));
488 // See https://doi.org/10.1021/acs.iecr.1c00847 for the exact value: OmegaB = 0.077796073903888455972;
489 auto b = 0.07780*Tc_K[i]*Ru/pc_Pa[i];
490 return forceeval(b*beta);
491 }
492
493 template<typename TType>
494 auto get_ai(std::size_t i, const TType& T) const {
495 auto alphai = forceeval(std::visit([&](auto& t) { return t(T); }, alphas[i]));
496 // See https://doi.org/10.1021/acs.iecr.1c00847
497 auto OmegaA = 0.45723552892138218938;
498 auto a = OmegaA*POW2(Tc_K[i]*Ru)/pc_Pa[i];
499 return forceeval(a*alphai);
500 }
501
502 template<typename TType, typename FractionsType>
503 auto get_ab(const TType& T, const FractionsType& z) const{
504 using numtype = std::common_type_t<TType, decltype(z[0])>;
505 numtype b = 0.0;
506 numtype a = 0.0;
507 std::size_t N = alphas.size();
508 for (auto i = 0U; i < N; ++i){
509 auto bi = get_bi(i, T);
510 auto ai = get_ai(i, T);
511 for (auto j = 0U; j < N; ++j){
512 auto bj = get_bi(j, T);
513 auto aj = get_ai(j, T);
514 b += z[i]*z[j]*(bi + bj)/2.0*(1.0 - lmat(i,j));
515 a += z[i]*z[j]*sqrt(ai*aj)*(1.0 - kmat(i,j));
516 }
517 }
518 return std::make_tuple(a, b);
519 }
520 template<typename TType, typename FractionsType>
521 auto get_c(const TType& /*T*/, const FractionsType& z) const{
522 using numtype = std::common_type_t<TType, decltype(z[0])>;
523 numtype c = 0.0;
524 std::size_t N = alphas.size();
525 for (auto i = 0U; i < N; ++i){
526 c += z[i]*cs_m3mol[i];
527 }
528 return c;
529 }
530
531 template<typename TType, typename RhoType, typename FractionsType>
532 auto alphar(const TType& T, const RhoType& rhoinit, const FractionsType& molefrac) const {
533 if (static_cast<std::size_t>(molefrac.size()) != alphas.size()) {
534 throw std::invalid_argument("Sizes do not match");
535 }
536 // First shift the volume by the volume translation
537 auto c = get_c(T, molefrac);
538 auto rho = 1.0/(1.0/rhoinit + c);
539 auto Delta1 = 1.0 + sqrt(2.0);
540 auto Delta2 = 1.0 - sqrt(2.0);
541 auto [a, b] = get_ab(T, molefrac);
542 auto Psiminus = -log(1.0 - b * rho);
543 auto Psiplus = log((Delta1 * b * rho + 1.0) / (Delta2 * b * rho + 1.0)) / (b * (Delta1 - Delta2));
544 auto val = Psiminus - a / (Ru * T) * Psiplus;
545 return forceeval(val);
546 }
547
553 auto superanc_rhoLV(double T, const std::optional<std::size_t> ifluid = std::nullopt) const {
554 if (Tc_K.size() != 1) {
555 throw std::invalid_argument("function only available for pure species");
556 }
557
558 std::valarray<double> molefracs(Tc_K.size()); molefracs = 1.0;
559 // If more than one component, must provide the ifluid argument
560 if(Tc_K.size() > 1){
561 if (!ifluid){
562 throw teqp::InvalidArgument("For mixtures, the argument ifluid must be provided");
563 }
564 if (ifluid.value() > Tc_K.size()-1){
565 throw teqp::InvalidArgument("ifluid must be less than "+std::to_string(Tc_K.size()));
566 }
567 molefracs = 0.0;
568 molefracs[ifluid.value()] = 1.0;
569 }
570
571 auto [a, b] = get_ab(T, molefracs);
572 auto Ttilde = R(molefracs)*T*b/a;
573 auto superanc_index = CubicSuperAncillary::PR_CODE;
574 return std::make_tuple(
575 CubicSuperAncillary::supercubic(superanc_index, CubicSuperAncillary::RHOL_CODE, Ttilde)/b,
576 CubicSuperAncillary::supercubic(superanc_index, CubicSuperAncillary::RHOV_CODE, Ttilde)/b
577 );
578 }
579};
580
581
594private:
595 const std::vector<double> delta_1, Tc_K, pc_Pa, k;
596 const Eigen::ArrayXXd kmat, lmat;
597 const double Ru;
598 const std::vector<double> a_c, b_c;
599
601 std::vector<double> get_(const nlohmann::json &j, const std::string& key) const { return j.at(key).get<std::vector<double>>(); }
602
604 auto get_yd1(double delta_1_){
605 return std::make_tuple(1 + cbrt(2*(1+delta_1_)) + cbrt(4/(1+delta_1_)), (1+delta_1_*delta_1_)/(1+delta_1_));
606 }
607
608 auto build_ac(){
609 std::vector<double> a_c_(delta_1.size());
610 for (auto i = 0U; i < delta_1.size(); ++i){
611 auto [y, d_1] = get_yd1(delta_1[i]);
612 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);
613 a_c_[i] = Omega_a*pow(Ru*Tc_K[i], 2)/pc_Pa[i];
614 }
615 return a_c_;
616 }
617 auto build_bc(){
618 std::vector<double> b_c_(delta_1.size());
619 for (auto i = 0U; i < delta_1.size(); ++i){
620 auto [y, d_1] = get_yd1(delta_1[i]);
621 auto Omega_b = 1.0/(3.0*y + d_1 - 1.0);
622 b_c_[i] = Omega_b*Ru*Tc_K[i]/pc_Pa[i];
623 }
624 return b_c_;
625 }
626public:
627
628 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()) {}
629
630 template<class VecType>
631 auto R(const VecType& /*molefrac*/) const {
632 return Ru;
633 }
634 auto get_delta_1() const { return delta_1; }
635 auto get_Tc_K() const { return Tc_K; }
636 auto get_pc_Pa() const { return pc_Pa; }
637 auto get_k() const { return k; }
638 auto get_kmat() const { return kmat; }
639 auto get_lmat() const { return lmat; }
640
641 template<typename TType>
642 auto get_bi(std::size_t i, const TType& /*T*/) const {
643 return b_c[i];
644 }
645
646 template<typename TType>
647 auto get_ai(std::size_t i, const TType& T) const {
648 return forceeval(a_c[i]*pow(3.0/(2.0+T/Tc_K[i]), k[i]));
649 }
650
651 template<typename TType, typename FractionsType>
652 auto get_ab(const TType& T, const FractionsType& z) const{
653 using numtype = std::common_type_t<TType, decltype(z[0])>;
654 numtype b = 0.0;
655 numtype a = 0.0;
656 std::size_t N = delta_1.size();
657 for (auto i = 0U; i < N; ++i){
658 auto bi = get_bi(i, T);
659 auto ai = get_ai(i, T);
660 for (auto j = 0U; j < N; ++j){
661 auto aj = get_ai(j, T);
662 auto bj = get_bi(j, T);
663
664 a += z[i]*z[j]*sqrt(ai*aj)*(1.0 - kmat(i,j));
665 b += z[i]*z[j]*(bi + bj)/2.0*(1.0 - lmat(i,j));
666
667 }
668 }
669 return std::make_tuple(a, b);
670 }
671
672 template<typename TType, typename RhoType, typename FractionsType>
673 auto alphar(const TType& T, const RhoType& rho, const FractionsType& molefrac) const {
674 if (static_cast<std::size_t>(molefrac.size()) != delta_1.size()) {
675 throw std::invalid_argument("Sizes do not match");
676 }
677
678 // The mixture Delta_1 is a mole-fraction-weighted average of the pure values of delta_1
679 const auto delta_1_view = Eigen::Map<const Eigen::ArrayXd>(&(delta_1[0]), delta_1.size());
680 const decltype(molefrac[0]) Delta1 = (molefrac*delta_1_view).eval().sum();
681
682 auto Delta2 = (1.0-Delta1)/(1.0+Delta1);
683 auto [a, b] = get_ab(T, molefrac);
684
685 auto Psiminus = -log(1.0 - b*rho);
686 auto Psiplus = log((Delta1 * b * rho + 1.0) / (Delta2 * b * rho + 1.0)) / (b * (Delta1 - Delta2));
687 auto val = Psiminus - a/(this->Ru*T)*Psiplus;
688 return forceeval(val);
689 }
690};
692
693
694
695}; // namespace teqp
696
The standard alpha function used by Peng-Robinson and SRK.
auto operator()(const TType &T) const
BasicAlphaFunction(NumType Tci, NumType mi)
auto get_ai(TType, IndexType i) const
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)
auto get_b(TType, const CompType &molefracs) const
void set_meta(const nlohmann::json &j)
void check_kmat(IndexType N)
auto alphar(const TType &T, const RhoType &rho, const MoleFracType &molefrac) const
auto get_a(TType T, const CompType &molefracs) const
std::valarray< NumType > ai
auto superanc_rhoLV(double T, std::optional< std::size_t > ifluid=std::nullopt) const
Eigen::ArrayXXd kmat
auto get_bi(TType, IndexType i) const
const AlphaFunctions alphas
std::valarray< NumType > bi
nlohmann::json meta
auto R(const VecType &) const
The Mathias-Copeman alpha function used by Peng-Robinson and SRK.
auto operator()(const TType &T) const
MathiasCopemanAlphaFunction(NumType Tci, const Eigen::Array3d &c)
auto get_ai(std::size_t i, const TType &T) const
auto get_ab(const TType &T, const FractionsType &z) const
auto alphar(const TType &T, const RhoType &rhoinit, const FractionsType &molefrac) const
QuantumCorrectedPR(const nlohmann::json &j)
auto get_bi(std::size_t i, const TType &T) const
auto R(const VecType &) const
auto superanc_rhoLV(double T, const std::optional< std::size_t > ifluid=std::nullopt) const
auto get_c(const TType &, const FractionsType &z) const
RKPRCismondi2005(const nlohmann::json &j)
auto get_bi(std::size_t i, const TType &) const
auto R(const VecType &) const
auto alphar(const TType &T, const RhoType &rho, const FractionsType &molefrac) const
auto get_ab(const TType &T, const FractionsType &z) const
auto get_ai(std::size_t i, const TType &T) const
The Twu alpha function used by Peng-Robinson and SRK.
auto operator()(const TType &T) const
TwuAlphaFunction(NumType Tci, const Eigen::Array3d &c)
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
auto make_generalizedcubic(const nlohmann::json &spec)
A JSON-based factory function for the generalized cubic + alpha.
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)
auto make_canonicalPR(const nlohmann::json &spec)
A JSON-based factory function for the canonical SRK model.
decltype(RKPRCismondi2005({})) RKPRCismondi2005_t
T pow2(const T &x)
Definition types.hpp:7
std::variant< BasicAlphaFunction< double >, TwuAlphaFunction< double >, MathiasCopemanAlphaFunction< double > > AlphaFunctionOptions
auto make_canonicalSRK(const nlohmann::json &spec)
A JSON-based factory function for the canonical SRK model.
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)
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)