teqp 0.19.1
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"
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 must be the same length as components");
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
146 template<typename TType, typename IndexType>
147 auto get_ai(TType /*T*/, IndexType i) const { return ai[i]; }
148
149 template<typename TType, typename IndexType>
150 auto get_bi(TType /*T*/, IndexType i) const { return bi[i]; }
151
152 template<typename IndexType>
153 void check_kmat(IndexType N) {
154 if (kmat.cols() != kmat.rows()) {
155 throw teqp::InvalidArgument("kmat rows [" + std::to_string(kmat.rows()) + "] and columns [" + std::to_string(kmat.cols()) + "] are not identical");
156 }
157 if (kmat.cols() == 0) {
158 kmat.resize(N, N); kmat.setZero();
159 }
160 else if (static_cast<std::size_t>(kmat.cols()) != N) {
161 throw teqp::InvalidArgument("kmat needs to be a square matrix the same size as the number of components [" + std::to_string(N) + "]");
162 }
163 };
164
165public:
166 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)
168 {
169 ai.resize(Tc_K.size());
170 bi.resize(Tc_K.size());
171 for (auto i = 0U; i < Tc_K.size(); ++i) {
172 ai[i] = OmegaA * pow2(Ru * Tc_K[i]) / pc_Pa[i];
173 bi[i] = OmegaB * Ru * Tc_K[i] / pc_Pa[i];
174 }
175 check_kmat(ai.size());
176 };
177
178 void set_meta(const nlohmann::json& j) { meta = j; }
179 auto get_meta() const { return meta; }
180 auto get_kmat() const { return kmat; }
181
186 auto superanc_rhoLV(double T, std::optional<std::size_t> ifluid = std::nullopt) const {
187
188 std::valarray<double> molefracs(ai.size()); molefracs = 1.0;
189
190 // If more than one component, must provide the ifluid argument
191 if(ai.size() > 1){
192 if (!ifluid){
193 throw teqp::InvalidArgument("For mixtures, the argument ifluid must be provided");
194 }
195 if (ifluid.value() > ai.size()-1){
196 throw teqp::InvalidArgument("ifluid must be less than "+std::to_string(ai.size()));
197 }
198 molefracs = 0.0;
199 molefracs[ifluid.value()] = 1.0;
200 }
201
202 auto b = get_b(T, molefracs);
203 auto a = get_a(T, molefracs);
204 auto Ttilde = R(molefracs)*T*b/a;
205 return std::make_tuple(
206 CubicSuperAncillary::supercubic(superanc_index, CubicSuperAncillary::RHOL_CODE, Ttilde)/b,
207 CubicSuperAncillary::supercubic(superanc_index, CubicSuperAncillary::RHOV_CODE, Ttilde)/b
208 );
209 }
210
211 const NumType Ru = get_R_gas<double>();
212
213 template<class VecType>
214 auto R(const VecType& /*molefrac*/) const {
215 return Ru;
216 }
217
218 template<typename TType, typename CompType>
219 auto get_a(TType T, const CompType& molefracs) const {
220 std::common_type_t<TType, decltype(molefracs[0])> a_ = 0.0;
221 for (auto i = 0U; i < molefracs.size(); ++i) {
222 auto alphai = forceeval(std::visit([&](auto& t) { return t(T); }, alphas[i]));
223 auto ai_ = forceeval(this->ai[i] * alphai);
224 for (auto j = 0U; j < molefracs.size(); ++j) {
225 auto alphaj = forceeval(std::visit([&](auto& t) { return t(T); }, alphas[j]));
226 auto aj_ = this->ai[j] * alphaj;
227 auto aij = forceeval((1 - kmat(i,j)) * sqrt(ai_ * aj_));
228 a_ = a_ + molefracs[i] * molefracs[j] * aij;
229 }
230 }
231 return forceeval(a_);
232 }
233
234 template<typename TType, typename CompType>
235 auto get_b(TType /*T*/, const CompType& molefracs) const {
236 std::common_type_t<TType, decltype(molefracs[0])> b_ = 0.0;
237 for (auto i = 0U; i < molefracs.size(); ++i) {
238 b_ = b_ + molefracs[i] * bi[i];
239 }
240 return forceeval(b_);
241 }
242
243 template<typename TType, typename RhoType, typename MoleFracType>
244 auto alphar(const TType& T,
245 const RhoType& rho,
246 const MoleFracType& molefrac) const
247 {
248 if (static_cast<std::size_t>(molefrac.size()) != alphas.size()) {
249 throw std::invalid_argument("Sizes do not match");
250 }
251 auto b = get_b(T, molefrac);
252 auto Psiminus = -log(1.0 - b * rho);
253 auto Psiplus = log((Delta1 * b * rho + 1.0) / (Delta2 * b * rho + 1.0)) / (b * (Delta1 - Delta2));
254 auto val = Psiminus - get_a(T, molefrac) / (Ru * T) * Psiplus;
255 return forceeval(val);
256 }
257};
258
259template <typename TCType, typename PCType, typename AcentricType>
260auto canonical_SRK(TCType Tc_K, PCType pc_Pa, AcentricType acentric, const std::optional<Eigen::ArrayXXd>& kmat = std::nullopt) {
261 double Delta1 = 1;
262 double Delta2 = 0;
263 AcentricType m = 0.48 + 1.574 * acentric - 0.176 * acentric * acentric;
264
265 std::vector<AlphaFunctionOptions> alphas;
266 for (auto i = 0U; i < Tc_K.size(); ++i) {
267 alphas.emplace_back(BasicAlphaFunction(Tc_K[i], m[i]));
268 }
269
270 // See https://doi.org/10.1021/acs.iecr.1c00847
271 double OmegaA = 1.0 / (9.0 * (cbrt(2) - 1));
272 double OmegaB = (cbrt(2) - 1) / 3;
273
274 nlohmann::json meta = {
275 {"Delta1", Delta1},
276 {"Delta2", Delta2},
277 {"OmegaA", OmegaA},
278 {"OmegaB", OmegaB},
279 {"kind", "Soave-Redlich-Kwong"}
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)));
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) {
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 };
324
325 const std::size_t N = m.size();
326 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)));
327 cub.set_meta(meta);
328 return cub;
329}
330
332inline auto make_canonicalPR(const nlohmann::json& spec){
333 std::valarray<double> Tc_K = spec.at("Tcrit / K"), pc_Pa = spec.at("pcrit / Pa"), acentric = spec.at("acentric");
334 Eigen::ArrayXXd kmat(0, 0);
335 if (spec.contains("kmat")){
336 kmat = build_square_matrix(spec.at("kmat"));
337 }
338 return canonical_PR(Tc_K, pc_Pa, acentric, kmat);
339}
340
342inline auto make_generalizedcubic(const nlohmann::json& spec){
343 // Tc, pc, and acentric factor must always be provided
344 std::valarray<double> Tc_K = spec.at("Tcrit / K"),
345 pc_Pa = spec.at("pcrit / Pa"),
346 acentric = spec.at("acentric");
347
348 // If kmat is provided, then collect it
349 std::optional<Eigen::ArrayXXd> kmat;
350 if (spec.contains("kmat")){
351 kmat = build_square_matrix(spec.at("kmat"));
352 }
353
354 int superanc_code = CubicSuperAncillary::UNKNOWN_CODE;
355
356 // Build the list of alpha functions, one per component
357 std::vector<AlphaFunctionOptions> alphas;
358
359 double Delta1, Delta2, OmegaA, OmegaB;
360 std::string kind = "custom";
361
362 if (spec.at("type") == "PR" ){
363 Delta1 = 1+sqrt(2.0);
364 Delta2 = 1-sqrt(2.0);
365 // See https://doi.org/10.1021/acs.iecr.1c00847
366 OmegaA = 0.45723552892138218938;
367 OmegaB = 0.077796073903888455972;
368 superanc_code = CubicSuperAncillary::PR_CODE;
369 kind = "Peng-Robinson";
370
371 if (!spec.contains("alpha")){
372 for (auto i = 0U; i < Tc_K.size(); ++i) {
373 double mi;
374 if (acentric[i] < 0.491) {
375 mi = 0.37464 + 1.54226*acentric[i] - 0.26992*pow2(acentric[i]);
376 }
377 else {
378 mi = 0.379642 + 1.48503*acentric[i] -0.164423*pow2(acentric[i]) + 0.016666*pow3(acentric[i]);
379 }
380 alphas.emplace_back(BasicAlphaFunction(Tc_K[i], mi));
381 }
382 }
383 else{
384 if (!spec["alpha"].is_array()){
385 throw teqp::InvalidArgument("alpha must be array of objects");
386 }
387 alphas = build_alpha_functions(Tc_K, spec.at("alpha"));
388 }
389 }
390 else if (spec.at("type") == "SRK"){
391 Delta1 = 1;
392 Delta2 = 0;
393 if (!spec.contains("alpha")){
394 for (auto i = 0U; i < Tc_K.size(); ++i) {
395 double mi = 0.48 + 1.574 * acentric[i] - 0.176 * acentric[i] * acentric[i];
396 alphas.emplace_back(BasicAlphaFunction(Tc_K[i], mi));
397 }
398 }
399 else{
400 if (!spec["alpha"].is_array()){
401 throw teqp::InvalidArgument("alpha must be array of objects");
402 }
403 alphas = build_alpha_functions(Tc_K, spec.at("alpha"));
404 }
405 // See https://doi.org/10.1021/acs.iecr.1c00847
406 OmegaA = 1.0 / (9.0 * (cbrt(2) - 1));
407 OmegaB = (cbrt(2) - 1) / 3;
408 superanc_code = CubicSuperAncillary::SRK_CODE;
409 kind = "Soave-Redlich-Kwong";
410 }
411 else{
412 // Generalized handling of generic cubics (not yet implemented)
413 throw teqp::InvalidArgument("Generic cubic EOS are not yet supported (open an issue on github if you want this)");
414 }
415
416 const std::size_t N = Tc_K.size();
417 nlohmann::json meta = {
418 {"Delta1", Delta1},
419 {"Delta2", Delta2},
420 {"OmegaA", OmegaA},
421 {"OmegaB", OmegaB},
422 {"kind", kind}
423 };
424 if (spec.contains("alpha")){
425 meta["alpha"] = spec.at("alpha");
426 }
427
428 auto cub = GenericCubic(Delta1, Delta2, OmegaA, OmegaB, superanc_code, Tc_K, pc_Pa, std::move(alphas), kmat.value_or(Eigen::ArrayXXd::Zero(N,N)));
429 cub.set_meta(meta);
430 return cub;
431}
432
434
439})
440
441struct AdvancedPRaEOptions{
443 double s = 2.0;
444 double CEoS = -sqrt(2.0)/2.0*log(1.0 + sqrt(2.0));
445};
446
447inline void from_json(const json& j, AdvancedPRaEOptions& o) {
448 j.at("brule").get_to(o.brule);
449 j.at("s").get_to(o.s);
450 j.at("CEoS").get_to(o.CEoS);
451}
452
456template<typename NumType>
458public:
459 template<typename TType, typename MoleFractions>
460 auto operator () (const TType& /*T*/, const MoleFractions& molefracs) const {
461 std::common_type_t<TType, decltype(molefracs[0])> val = 0.0;
462 return val;
463 }
464};
465
486template<typename NumType>
488
489public:
490 const double R = 8.31446261815324;
491 const std::vector<double> b;
492 const Eigen::ArrayXXd m, n;
493 WilsonResidualHelmholtzOverRT(const std::vector<double>& b, const Eigen::ArrayXXd& m, const Eigen::ArrayXXd& n) : b(b), m(m), n(n) {};
494
495 template<typename TType, typename MoleFractions>
496 auto combinatorial(const TType& /*T*/, const MoleFractions& molefracs) const {
497 if (b.size() != static_cast<std::size_t>(molefracs.size())){
498 throw teqp::InvalidArgument("Bad size of molefracs");
499 }
500
501 using TYPE = std::common_type_t<TType, decltype(molefracs[0])>;
502 // The denominator in Phi
503 TYPE Vtot = 0.0;
504 for (auto i = 0U; i < molefracs.size(); ++i){
505 auto v_i = b[i];
506 Vtot += molefracs[i]*v_i;
507 }
508
509 TYPE summer = 0.0;
510 for (auto i = 0U; i < molefracs.size(); ++i){
511 auto v_i = b[i];
512 // The ratio phi_i/z_i is expressed like this to better handle
513 // the case of z_i = 0, which would otherwise be a divide by zero
514 // in the case that the composition of one component is zero
515 auto phi_i_over_z_i = v_i/Vtot;
516 summer += molefracs[i]*log(phi_i_over_z_i);
517 }
518 return summer;
519 };
520
521 template<typename TType>
522 auto get_Aij(std::size_t i, std::size_t j, const TType& T) const{
523 return forceeval(m(i,j)*T + n(i,j));
524 }
525
526 template<typename TType, typename MoleFractions>
527 auto total(const TType& T, const MoleFractions& molefracs) const {
528
529 using TYPE = std::common_type_t<TType, decltype(molefracs[0])>;
530 TYPE summer = 0.0;
531 for (auto i = 0U; i < molefracs.size(); ++i){
532 auto v_i = b[i];
533 TYPE summerj = 0.0;
534 for (auto j = 0U; j < molefracs.size(); ++j){
535 auto v_j = b[j];
536 auto Aij = get_Aij(i,j,T);
537 auto Omega_ji = v_j/v_i*exp(-Aij/T);
538 summerj += molefracs[j]*Omega_ji;
539 }
540 summer += molefracs[i]*log(summerj);
541 }
542 return forceeval(-summer);
543 };
544
545 // Returns ares/RT
546 template<typename TType, typename MoleFractions>
547 auto operator () (const TType& T, const MoleFractions& molefracs) const {
548 return forceeval(total(T, molefracs) - combinatorial(T, molefracs));
549 }
550};
551
552using ResidualHelmholtzOverRTVariant = std::variant<NullResidualHelmholtzOverRT<double>, WilsonResidualHelmholtzOverRT<double>>;
553
558template <typename NumType, typename AlphaFunctions = std::vector<AlphaFunctionOptions>>
560public:
561 // Hard-coded values for Peng-Robinson
562 const NumType Delta1 = 1+sqrt(2.0);
563 const NumType Delta2 = 1-sqrt(2.0);
564 // See https://doi.org/10.1021/acs.iecr.1c00847
565 const NumType OmegaA = 0.45723552892138218938;
566 const NumType OmegaB = 0.077796073903888455972;
568
569
570protected:
571
572 std::valarray<NumType> Tc_K, pc_Pa;
573
574 std::valarray<NumType> ai, bi;
575
576 const AlphaFunctions alphas;
578 Eigen::ArrayXXd lmat;
579
581 const double s;
582 const double CEoS;
583
584 nlohmann::json meta;
585
586 template<typename TType, typename IndexType>
587 auto get_ai(TType& T, IndexType i) const {
588 auto alphai = std::visit([&](auto& t) { return t(T); }, alphas[i]);
589 return forceeval(ai[i]*alphai);
590 }
591
592 template<typename TType, typename IndexType>
593 auto get_bi(TType& /*T*/, IndexType i) const { return bi[i]; }
594
595 template<typename IndexType>
596 void check_lmat(IndexType N) {
597 if (lmat.cols() != lmat.rows()) {
598 throw teqp::InvalidArgument("lmat rows [" + std::to_string(lmat.rows()) + "] and columns [" + std::to_string(lmat.cols()) + "] are not identical");
599 }
600 if (lmat.cols() == 0) {
601 lmat.resize(N, N); lmat.setZero();
602 }
603 else if (lmat.cols() != static_cast<Eigen::Index>(N)) {
604 throw teqp::InvalidArgument("lmat needs to be a square matrix the same size as the number of components [" + std::to_string(N) + "]");
605 }
606 };
607
608public:
609 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 = {})
610 : Tc_K(Tc_K), pc_Pa(pc_Pa), alphas(alphas), ares(ares), lmat(lmat), brule(options.brule), s(options.s), CEoS(options.CEoS)
611 {
612 ai.resize(Tc_K.size());
613 bi.resize(Tc_K.size());
614 for (auto i = 0U; i < Tc_K.size(); ++i) {
615 ai[i] = OmegaA * pow2(Ru * Tc_K[i]) / pc_Pa[i];
616 bi[i] = OmegaB * Ru * Tc_K[i] / pc_Pa[i];
617 }
618 check_lmat(ai.size());
619 };
620
621 void set_meta(const nlohmann::json& j) { meta = j; }
622 auto get_meta() const { return meta; }
623 auto get_lmat() const { return lmat; }
624 auto get_Tc_K() const { return Tc_K; }
625 auto get_pc_Pa() const { return pc_Pa; }
626
627 static double get_bi(double Tc_K, double pc_Pa){
628 const NumType OmegaB = 0.077796073903888455972;
629 const NumType R = 8.31446261815324;
630 return OmegaB*R*Tc_K/pc_Pa;
631 }
632
637 auto superanc_rhoLV(double T, std::optional<std::size_t> ifluid = std::nullopt) const {
638
639 std::valarray<double> molefracs(ai.size()); molefracs = 1.0;
640
641 // If more than one component, must provide the ifluid argument
642 if(ai.size() > 1){
643 if (!ifluid){
644 throw teqp::InvalidArgument("For mixtures, the argument ifluid must be provided");
645 }
646 if (ifluid.value() > ai.size()-1){
647 throw teqp::InvalidArgument("ifluid must be less than "+std::to_string(ai.size()));
648 }
649 molefracs = 0.0;
650 molefracs[ifluid.value()] = 1.0;
651 }
652
653 auto b = get_b(T, molefracs);
654 auto a = get_am_over_bm(T, molefracs)*b;
655 auto Ttilde = R(molefracs)*T*b/a;
656 return std::make_tuple(
657 CubicSuperAncillary::supercubic(superanc_code, CubicSuperAncillary::RHOL_CODE, Ttilde)/b,
658 CubicSuperAncillary::supercubic(superanc_code, CubicSuperAncillary::RHOV_CODE, Ttilde)/b
659 );
660 }
661
662 const NumType Ru = get_R_gas<double>();
663
664 template<class VecType>
665 auto R(const VecType& /*molefrac*/) const {
666 return Ru;
667 }
668
669 template<typename TType, typename CompType>
670 auto get_a(TType T, const CompType& molefracs) const {
671 return forceeval(get_am_over_bm(T, molefracs)*get_b(T, molefracs));
672 }
673
674 template<typename TType, typename CompType>
675 auto get_am_over_bm(TType T, const CompType& molefracs) const {
676 auto aEresRT = std::visit([&](auto& aresRTfunc) { return aresRTfunc(T, molefracs); }, ares); // aEres/RT, so a non-dimensional quantity
677 std::common_type_t<TType, decltype(molefracs[0])> summer = aEresRT*Ru*T/CEoS;
678 for (auto i = 0U; i < molefracs.size(); ++i) {
679 summer += molefracs[i]*get_ai(T,i)/get_bi(T,i);
680 }
681 return forceeval(summer);
682 }
683
684 template<typename TType, typename CompType>
685 auto get_b(TType T, const CompType& molefracs) const {
686 std::common_type_t<TType, decltype(molefracs[0])> b_ = 0.0;
687
688 switch (brule){
690 for (auto i = 0U; i < molefracs.size(); ++i) {
691 auto bi_ = get_bi(T, i);
692 for (auto j = 0U; j < molefracs.size(); ++j) {
693 auto bj_ = get_bi(T, j);
694
695 auto bij = (1 - lmat(i,j)) * pow((pow(bi_, 1.0/s) + pow(bj_, 1.0/s))/2.0, s);
696 b_ += molefracs[i] * molefracs[j] * bij;
697 }
698 }
699 break;
701 for (auto i = 0U; i < molefracs.size(); ++i) {
702 b_ += molefracs[i] * get_bi(T, i);
703 }
704 break;
705 default:
706 throw teqp::InvalidArgument("Mixing rule for b is invalid");
707 }
708 return forceeval(b_);
709 }
710
711 template<typename TType, typename RhoType, typename MoleFracType>
712 auto alphar(const TType& T,
713 const RhoType& rho,
714 const MoleFracType& molefrac) const
715 {
716 if (static_cast<std::size_t>(molefrac.size()) != alphas.size()) {
717 throw std::invalid_argument("Sizes do not match");
718 }
719 auto b = get_b(T, molefrac);
720 auto a = get_am_over_bm(T, molefrac)*b;
721 auto Psiminus = -log(1.0 - b * rho);
722 auto Psiplus = log((Delta1 * b * rho + 1.0) / (Delta2 * b * rho + 1.0)) / (b * (Delta1 - Delta2));
723 auto val = Psiminus - a / (Ru * T) * Psiplus;
724 return forceeval(val);
725 }
726};
727
728inline auto make_AdvancedPRaEres(const nlohmann::json& j){
729
730 std::valarray<double> Tc_K = j.at("Tcrit / K");
731 std::valarray<double> pc_Pa = j.at("pcrit / Pa");
732
733 std::vector<AlphaFunctionOptions> alphas = build_alpha_functions(Tc_K, j.at("alphas"));
734
735 auto get_ares_model = [&](const nlohmann::json& armodel) -> ResidualHelmholtzOverRTVariant {
736
737 std::string type = armodel.at("type");
738 if (type == "Wilson"){
739 std::vector<double> b;
740 for (auto i = 0U; i < Tc_K.size(); ++i){
741 b.push_back(teqp::AdvancedPRaEres<double>::get_bi(Tc_K[i], pc_Pa[i]));
742 }
743 auto mWilson = build_square_matrix(armodel.at("m"));
744 auto nWilson = build_square_matrix(armodel.at("n"));
745 return WilsonResidualHelmholtzOverRT<double>(b, mWilson, nWilson);
746 }
747 else{
748 throw teqp::InvalidArgument("bad type of ares model: " + type);
749 }
750 };
751 auto aresmodel = get_ares_model(j.at("aresmodel"));
752
753 AdvancedPRaEOptions options = j.at("options");
754 auto model = teqp::AdvancedPRaEres<double>(Tc_K, pc_Pa, alphas, aresmodel, Eigen::ArrayXXd::Zero(2, 2), options);
755 return model;
756}
757
759
769private:
770 const std::vector<double> Tc_K, pc_Pa;
771 const std::vector<AlphaFunctionOptions> alphas;
772 const std::vector<double> As, Bs, cs_m3mol;
773 const Eigen::ArrayXXd kmat, lmat;
774
775 auto build_alphas(const nlohmann::json& j){
776 std::vector<AlphaFunctionOptions> alphas_;
777 std::vector<double> L = j.at("Ls"), M = j.at("Ms"), N = j.at("Ns");
778 if (L.size() != M.size() || M.size() != N.size()){
779 throw teqp::InvalidArgument("L,M,N must all be the same length");
780 }
781 for (auto i = 0U; i < L.size(); ++i){
782 auto coeffs = (Eigen::Array3d() << L[i], M[i], N[i]).finished();
783 alphas_.emplace_back(TwuAlphaFunction(Tc_K[i], coeffs));
784 }
785 return alphas_;
786 }
788 std::vector<double> get_(const nlohmann::json &j, const std::string& k) const { return j.at(k).get<std::vector<double>>(); }
789public:
790
791 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"))) {}
792
793 const double Ru = get_R_gas<double>();
794
795 template<class VecType>
796 auto R(const VecType& /*molefrac*/) const {
797 return Ru;
798 }
799 auto get_kmat() const { return kmat; }
800 auto get_lmat() const { return lmat; }
801 auto get_Tc_K() const { return Tc_K; }
802 auto get_pc_Pa() const { return pc_Pa; }
803
804 template<typename TType>
805 auto get_bi(std::size_t i, const TType& T) const {
806 auto beta = POW3(1.0 + As[i]/(T+Bs[i]))/POW3(1.0+As[i]/(Tc_K[i]+Bs[i]));
807 // See https://doi.org/10.1021/acs.iecr.1c00847 for the exact value: OmegaB = 0.077796073903888455972;
808 auto b = 0.07780*Tc_K[i]*Ru/pc_Pa[i];
809 return forceeval(b*beta);
810 }
811
812 template<typename TType>
813 auto get_ai(std::size_t i, const TType& T) const {
814 auto alphai = forceeval(std::visit([&](auto& t) { return t(T); }, alphas[i]));
815 // See https://doi.org/10.1021/acs.iecr.1c00847
816 auto OmegaA = 0.45723552892138218938;
817 auto a = OmegaA*POW2(Tc_K[i]*Ru)/pc_Pa[i];
818 return forceeval(a*alphai);
819 }
820
821 template<typename TType, typename FractionsType>
822 auto get_ab(const TType& T, const FractionsType& z) const{
823 using numtype = std::common_type_t<TType, decltype(z[0])>;
824 numtype b = 0.0;
825 numtype a = 0.0;
826 std::size_t N = alphas.size();
827 for (auto i = 0U; i < N; ++i){
828 auto bi = get_bi(i, T);
829 auto ai = get_ai(i, T);
830 for (auto j = 0U; j < N; ++j){
831 auto bj = get_bi(j, T);
832 auto aj = get_ai(j, T);
833 b += z[i]*z[j]*(bi + bj)/2.0*(1.0 - lmat(i,j));
834 a += z[i]*z[j]*sqrt(ai*aj)*(1.0 - kmat(i,j));
835 }
836 }
837 return std::make_tuple(a, b);
838 }
839 template<typename TType, typename FractionsType>
840 auto get_c(const TType& /*T*/, const FractionsType& z) const{
841 using numtype = std::common_type_t<TType, decltype(z[0])>;
842 numtype c = 0.0;
843 std::size_t N = alphas.size();
844 for (auto i = 0U; i < N; ++i){
845 c += z[i]*cs_m3mol[i];
846 }
847 return c;
848 }
849
850 template<typename TType, typename RhoType, typename FractionsType>
851 auto alphar(const TType& T, const RhoType& rhoinit, const FractionsType& molefrac) const {
852 if (static_cast<std::size_t>(molefrac.size()) != alphas.size()) {
853 throw std::invalid_argument("Sizes do not match");
854 }
855 // First shift the volume by the volume translation
856 auto c = get_c(T, molefrac);
857 auto rho = 1.0/(1.0/rhoinit + c);
858 auto Delta1 = 1.0 + sqrt(2.0);
859 auto Delta2 = 1.0 - sqrt(2.0);
860 auto [a, b] = get_ab(T, molefrac);
861 auto Psiminus = -log(1.0 - b * rho);
862 auto Psiplus = log((Delta1 * b * rho + 1.0) / (Delta2 * b * rho + 1.0)) / (b * (Delta1 - Delta2));
863 auto val = Psiminus - a / (Ru * T) * Psiplus;
864 return forceeval(val);
865 }
866
869 auto superanc_rhoLV(double T) const {
870 if (Tc_K.size() != 1) {
871 throw std::invalid_argument("function only available for pure species");
872 }
873 const std::valarray<double> z = { 1.0 };
874 auto [a, b] = get_ab(T, z);
875 auto Ttilde = R(z)*T*b/a;
876 auto superanc_index = CubicSuperAncillary::PR_CODE;
877 return std::make_tuple(
878 CubicSuperAncillary::supercubic(superanc_index, CubicSuperAncillary::RHOL_CODE, Ttilde)/b,
879 CubicSuperAncillary::supercubic(superanc_index, CubicSuperAncillary::RHOV_CODE, Ttilde)/b
880 );
881 }
882};
883
884
897public:
898 const double Ru = get_R_gas<double>();
899
900private:
901 const std::vector<double> delta_1, Tc_K, pc_Pa, k;
902 const Eigen::ArrayXXd kmat, lmat;
903 const std::vector<double> a_c, b_c;
904
906 std::vector<double> get_(const nlohmann::json &j, const std::string& key) const { return j.at(key).get<std::vector<double>>(); }
907
909 auto get_yd1(double delta_1_){
910 return std::make_tuple(1 + cbrt(2*(1+delta_1_)) + cbrt(4/(1+delta_1_)), (1+delta_1_*delta_1_)/(1+delta_1_));
911 }
912
913 auto build_ac(){
914 std::vector<double> a_c_(delta_1.size());
915 for (auto i = 0U; i < delta_1.size(); ++i){
916 auto [y, d_1] = get_yd1(delta_1[i]);
917 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);
918 a_c_[i] = Omega_a*pow(Ru*Tc_K[i], 2)/pc_Pa[i];
919 }
920 return a_c_;
921 }
922 auto build_bc(){
923 std::vector<double> b_c_(delta_1.size());
924 for (auto i = 0U; i < delta_1.size(); ++i){
925 auto [y, d_1] = get_yd1(delta_1[i]);
926 auto Omega_b = 1.0/(3.0*y + d_1 - 1.0);
927 b_c_[i] = Omega_b*Ru*Tc_K[i]/pc_Pa[i];
928 }
929 return b_c_;
930 }
931public:
932
933 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"))), a_c(build_ac()), b_c(build_bc()) {}
934
935 template<class VecType>
936 auto R(const VecType& /*molefrac*/) const {
937 return Ru;
938 }
939 auto get_delta_1() const { return delta_1; }
940 auto get_Tc_K() const { return Tc_K; }
941 auto get_pc_Pa() const { return pc_Pa; }
942 auto get_k() const { return k; }
943 auto get_kmat() const { return kmat; }
944 auto get_lmat() const { return lmat; }
945
946 template<typename TType>
947 auto get_bi(std::size_t i, const TType& /*T*/) const {
948 return b_c[i];
949 }
950
951 template<typename TType>
952 auto get_ai(std::size_t i, const TType& T) const {
953 return forceeval(a_c[i]*pow(3.0/(2.0+T/Tc_K[i]), k[i]));
954 }
955
956 template<typename TType, typename FractionsType>
957 auto get_ab(const TType& T, const FractionsType& z) const{
958 using numtype = std::common_type_t<TType, decltype(z[0])>;
959 numtype b = 0.0;
960 numtype a = 0.0;
961 std::size_t N = delta_1.size();
962 for (auto i = 0U; i < N; ++i){
963 auto bi = get_bi(i, T);
964 auto ai = get_ai(i, T);
965 for (auto j = 0U; j < N; ++j){
966 auto aj = get_ai(j, T);
967 auto bj = get_bi(j, T);
968
969 a += z[i]*z[j]*sqrt(ai*aj)*(1.0 - kmat(i,j));
970 b += z[i]*z[j]*(bi + bj)/2.0*(1.0 - lmat(i,j));
971
972 }
973 }
974 return std::make_tuple(a, b);
975 }
976
977 template<typename TType, typename RhoType, typename FractionsType>
978 auto alphar(const TType& T, const RhoType& rho, const FractionsType& molefrac) const {
979 if (static_cast<std::size_t>(molefrac.size()) != delta_1.size()) {
980 throw std::invalid_argument("Sizes do not match");
981 }
982
983 // The mixture Delta_1 is a mole-fraction-weighted average of the pure values of delta_1
984 const auto delta_1_view = Eigen::Map<const Eigen::ArrayXd>(&(delta_1[0]), delta_1.size());
985 const decltype(molefrac[0]) Delta1 = (molefrac*delta_1_view).eval().sum();
986
987 auto Delta2 = (1.0-Delta1)/(1.0+Delta1);
988 auto [a, b] = get_ab(T, molefrac);
989
990 auto Psiminus = -log(1.0 - b*rho);
991 auto Psiplus = log((Delta1 * b * rho + 1.0) / (Delta2 * b * rho + 1.0)) / (b * (Delta1 - Delta2));
992 auto val = Psiminus - a/(Ru*T)*Psiplus;
993 return forceeval(val);
994 }
995};
997
998
999
1000}; // namespace teqp
auto get_a(TType T, const CompType &molefracs) const
Definition cubics.hpp:670
auto get_b(TType T, const CompType &molefracs) const
Definition cubics.hpp:685
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:609
void check_lmat(IndexType N)
Definition cubics.hpp:596
nlohmann::json meta
Definition cubics.hpp:584
std::valarray< NumType > Tc_K
Definition cubics.hpp:572
Eigen::ArrayXXd lmat
Definition cubics.hpp:578
static double get_bi(double Tc_K, double pc_Pa)
Definition cubics.hpp:627
const ResidualHelmholtzOverRTVariant ares
Definition cubics.hpp:577
auto get_Tc_K() const
Definition cubics.hpp:624
std::valarray< NumType > ai
Definition cubics.hpp:574
auto get_ai(TType &T, IndexType i) const
Definition cubics.hpp:587
const AlphaFunctions alphas
Definition cubics.hpp:576
const AdvancedPRaEMixingRules brule
Definition cubics.hpp:580
const NumType Delta2
Definition cubics.hpp:563
const NumType OmegaB
Definition cubics.hpp:566
void set_meta(const nlohmann::json &j)
Definition cubics.hpp:621
std::valarray< NumType > pc_Pa
Definition cubics.hpp:572
auto get_bi(TType &, IndexType i) const
Definition cubics.hpp:593
std::valarray< NumType > bi
Definition cubics.hpp:574
const double CEoS
Definition cubics.hpp:582
const int superanc_code
Definition cubics.hpp:567
const NumType Ru
Definition cubics.hpp:662
auto R(const VecType &) const
Universal gas constant, exact number.
Definition cubics.hpp:665
const NumType Delta1
Definition cubics.hpp:562
auto get_lmat() const
Definition cubics.hpp:623
auto get_pc_Pa() const
Definition cubics.hpp:625
auto superanc_rhoLV(double T, std::optional< std::size_t > ifluid=std::nullopt) const
Definition cubics.hpp:637
auto get_meta() const
Definition cubics.hpp:622
const NumType OmegaA
Definition cubics.hpp:565
auto get_am_over_bm(TType T, const CompType &molefracs) const
Definition cubics.hpp:675
auto alphar(const TType &T, const RhoType &rho, const MoleFracType &molefrac) const
Definition cubics.hpp:712
The standard alpha function used by Peng-Robinson and SRK.
Definition cubics.hpp:29
auto operator()(const TType &T) const
Definition cubics.hpp:37
BasicAlphaFunction(NumType Tci, NumType mi)
Definition cubics.hpp:34
auto get_ai(TType, IndexType i) const
Definition cubics.hpp:147
const NumType OmegaB
Definition cubics.hpp:139
const NumType OmegaA
Definition cubics.hpp:139
auto get_b(TType, const CompType &molefracs) const
Definition cubics.hpp:235
const NumType Delta2
Definition cubics.hpp:139
void set_meta(const nlohmann::json &j)
Definition cubics.hpp:178
void check_kmat(IndexType N)
Definition cubics.hpp:153
auto alphar(const TType &T, const RhoType &rho, const MoleFracType &molefrac) const
Definition cubics.hpp:244
auto get_a(TType T, const CompType &molefracs) const
Definition cubics.hpp:219
std::valarray< NumType > ai
Definition cubics.hpp:138
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)
Definition cubics.hpp:166
auto superanc_rhoLV(double T, std::optional< std::size_t > ifluid=std::nullopt) const
Definition cubics.hpp:186
const NumType Delta1
Definition cubics.hpp:139
Eigen::ArrayXXd kmat
Definition cubics.hpp:142
const NumType Ru
Definition cubics.hpp:211
auto get_kmat() const
Definition cubics.hpp:180
auto get_bi(TType, IndexType i) const
Definition cubics.hpp:150
const AlphaFunctions alphas
Definition cubics.hpp:141
std::valarray< NumType > bi
Definition cubics.hpp:138
auto get_meta() const
Definition cubics.hpp:179
nlohmann::json meta
Definition cubics.hpp:144
auto R(const VecType &) const
Universal gas constant, exact number.
Definition cubics.hpp:214
The Mathias-Copeman alpha function used by Peng-Robinson and SRK.
Definition cubics.hpp:77
auto operator()(const TType &T) const
Definition cubics.hpp:88
MathiasCopemanAlphaFunction(NumType Tci, const Eigen::Array3d &c)
Definition cubics.hpp:82
auto operator()(const TType &, const MoleFractions &molefracs) const
Definition cubics.hpp:460
auto get_ai(std::size_t i, const TType &T) const
Definition cubics.hpp:813
auto get_pc_Pa() const
Definition cubics.hpp:802
auto get_ab(const TType &T, const FractionsType &z) const
Definition cubics.hpp:822
auto superanc_rhoLV(double T) const
Definition cubics.hpp:869
auto alphar(const TType &T, const RhoType &rhoinit, const FractionsType &molefrac) const
Definition cubics.hpp:851
auto get_Tc_K() const
Definition cubics.hpp:801
QuantumCorrectedPR(const nlohmann::json &j)
Definition cubics.hpp:791
auto get_bi(std::size_t i, const TType &T) const
Definition cubics.hpp:805
auto R(const VecType &) const
Universal gas constant, exact number.
Definition cubics.hpp:796
auto get_kmat() const
Definition cubics.hpp:799
auto get_lmat() const
Definition cubics.hpp:800
auto get_c(const TType &, const FractionsType &z) const
Definition cubics.hpp:840
RKPRCismondi2005(const nlohmann::json &j)
Definition cubics.hpp:933
auto get_bi(std::size_t i, const TType &) const
Definition cubics.hpp:947
auto get_delta_1() const
Definition cubics.hpp:939
auto R(const VecType &) const
Definition cubics.hpp:936
auto alphar(const TType &T, const RhoType &rho, const FractionsType &molefrac) const
Definition cubics.hpp:978
auto get_k() const
Definition cubics.hpp:942
auto get_ab(const TType &T, const FractionsType &z) const
Definition cubics.hpp:957
auto get_kmat() const
Definition cubics.hpp:943
auto get_Tc_K() const
Definition cubics.hpp:940
auto get_pc_Pa() const
Definition cubics.hpp:941
auto get_ai(std::size_t i, const TType &T) const
Definition cubics.hpp:952
auto get_lmat() const
Definition cubics.hpp:944
The Twu alpha function used by Peng-Robinson and SRK.
Definition cubics.hpp:49
auto operator()(const TType &T) const
Definition cubics.hpp:60
TwuAlphaFunction(NumType Tci, const Eigen::Array3d &c)
Definition cubics.hpp:54
const std::vector< double > b
Definition cubics.hpp:491
auto operator()(const TType &T, const MoleFractions &molefracs) const
Definition cubics.hpp:547
WilsonResidualHelmholtzOverRT(const std::vector< double > &b, const Eigen::ArrayXXd &m, const Eigen::ArrayXXd &n)
Definition cubics.hpp:493
auto combinatorial(const TType &, const MoleFractions &molefracs) const
Definition cubics.hpp:496
auto total(const TType &T, const MoleFractions &molefracs) const
Definition cubics.hpp:527
auto get_Aij(std::size_t i, std::size_t j, const TType &T) const
Definition cubics.hpp:522
auto POW2(const A &x)
auto build_square_matrix
T pow3(const T &x)
Definition types.hpp:8
AdvancedPRaEMixingRules
Definition cubics.hpp:433
auto make_generalizedcubic(const nlohmann::json &spec)
A JSON-based factory function for the generalized cubic + alpha.
Definition cubics.hpp:342
auto canonical_PR(TCType Tc_K, PCType pc_Pa, AcentricType acentric, const std::optional< Eigen::ArrayXXd > &kmat=std::nullopt)
Definition cubics.hpp:298
auto make_AdvancedPRaEres(const nlohmann::json &j)
Definition cubics.hpp:728
void from_json(const json &j, AdvancedPRaEOptions &o)
Definition cubics.hpp:447
decltype(make_AdvancedPRaEres({})) advancedPRaEres_t
Definition cubics.hpp:758
auto POW3(const A &x)
auto canonical_SRK(TCType Tc_K, PCType pc_Pa, AcentricType acentric, const std::optional< Eigen::ArrayXXd > &kmat=std::nullopt)
Definition cubics.hpp:260
auto make_canonicalPR(const nlohmann::json &spec)
A JSON-based factory function for the canonical SRK model.
Definition cubics.hpp:332
decltype(RKPRCismondi2005({})) RKPRCismondi2005_t
Definition cubics.hpp:996
std::variant< NullResidualHelmholtzOverRT< double >, WilsonResidualHelmholtzOverRT< double > > ResidualHelmholtzOverRTVariant
Definition cubics.hpp:552
T pow2(const T &x)
Definition types.hpp:7
std::variant< BasicAlphaFunction< double >, TwuAlphaFunction< double >, MathiasCopemanAlphaFunction< double > > AlphaFunctionOptions
Definition cubics.hpp:95
auto make_canonicalSRK(const nlohmann::json &spec)
A JSON-based factory function for the canonical SRK model.
Definition cubics.hpp:288
NLOHMANN_JSON_SERIALIZE_ENUM(AdvancedPRaEMixingRules, { {AdvancedPRaEMixingRules::knotspecified, nullptr}, {AdvancedPRaEMixingRules::kLinear, "Linear"}, {AdvancedPRaEMixingRules::kQuadratic, "Quadratic"}, }) struct AdvancedPRaEOptions
Definition cubics.hpp:435
auto pow(const double &x, const double &e)
Definition types.hpp:189
auto forceeval(T &&expr)
Definition types.hpp:46
auto build_alpha_functions(const TC &Tc_K, const nlohmann::json &jalphas)
Definition cubics.hpp:98