CoolProp 8.0.0
An open-source fluid property and humid air property database
CubicBackend.h
Go to the documentation of this file.
1/*
2
3The goal of this backend is to allow the Helmholtz-based structure for cubics and to replace the entire
4multi-fluid model with a one-fluid model. The other changes are relatively trivial. The primary
5change is to replace the core residual Helmholtz energy derivatives from HelmholtzEOSMixtureBackend
6with the derivatives from this class.
7
8The core code for the Helmholtz translations is from the publication
9"Helmholtz energy translations for common cubic equations of state for use in one-fluid and multi-fluid mixture models"
10by Ian H. Bell and Andreas Jaeger, J. Res. NIST, 2016
11
12*/
13
14#ifndef CUBICBACKEND_H_
15#define CUBICBACKEND_H_
16
17#include <array>
18
21#include "GeneralizedCubic.h"
22#include "CubicsLibrary.h"
26#include "CoolProp/Exceptions.h"
28#include <algorithm>
29#include <vector>
30
31namespace CoolProp {
32
33// Forward declaration for use in initialization of AbstractCubicBackend
34class CubicResidualHelmholtz;
35
37{
38 protected:
39 shared_ptr<AbstractCubic> cubic;
40 std::vector<CubicLibrary::CubicsValues> components;
41 public:
43 void setup(bool generate_SatL_and_SatV = true);
44
65
68
70 shared_ptr<AbstractCubic>& get_cubic() {
71 return cubic;
72 };
73
74 std::vector<std::string> calc_fluid_names() override;
75
76 bool using_mole_fractions() override {
77 return true;
78 };
79 bool using_mass_fractions() override {
80 return false;
81 };
82 bool using_volu_fractions() override {
83 return false;
84 };
85
86 void set_mass_fractions(const std::vector<CoolPropDbl>& mass_fractions) override {
87 throw NotImplementedError("Mass composition has not been implemented.");
88 };
89 void set_volu_fractions(const std::vector<CoolPropDbl>& volu_fractions) override {
90 throw NotImplementedError("Volume composition has not been implemented.");
91 };
92 const std::vector<CoolPropDbl>& get_mole_fractions() override {
93 return this->mole_fractions;
94 };
95
96 const double get_fluid_constant(std::size_t i, parameters param) const override {
97 switch (param) {
98 case iP_critical:
99 return cubic->get_pc()[i];
100 case iT_reducing:
101 case iT_critical:
102 return cubic->get_Tc()[i];
103 case iacentric_factor:
104 return cubic->get_acentric()[i];
105 case imolar_mass:
106 return components[i].molemass;
107 case iT_triple:
108 return HelmholtzEOSMixtureBackend::get_components()[i].EOS().sat_min_liquid.T; // From the base class data structure
109 case iP_triple:
110 return HelmholtzEOSMixtureBackend::get_components()[i].EOS().sat_min_liquid.p; // From the base class data structure
113 return components[i].rhomolarc;
114 case igas_constant:
115 return get_config_double(R_U_CODATA);
116 default:
117 throw ValueError(format("I don't know what to do with this fluid constant: %s", get_parameter_information(param, "short").c_str()));
118 }
119 }
120
122 std::string fluid_param_string(const std::string&) override;
125 return cubic->get_R_u();
126 };
128 SimpleState calc_reducing_state_nocache(const std::vector<CoolPropDbl>& mole_fractions) override {
129 SimpleState reducing;
130 reducing.T = cubic->get_Tr();
131 reducing.rhomolar = cubic->get_rhor();
132 return reducing;
133 };
135 return 0.9 / get_cubic()->bm_term(mole_fractions);
136 };
138 return _rhomolar / get_cubic()->get_rhor();
139 };
141 return get_cubic()->get_Tr() / _T;
142 };
143 std::vector<double> spinodal_densities();
144
147 return cubic->get_Tc()[0];
148 } else {
150 }
151 };
154 return cubic->get_pc()[0];
155 } else {
157 }
158 };
161 return cubic->get_acentric()[0];
162 } else {
163 throw ValueError("acentric factor cannot be calculated for mixtures");
164 }
165 }
168 // Curve fit from all the pure fluids in CoolProp (thanks to recommendation of A. Kazakov)
169 double v_c_Lmol = 2.14107171795 * (cubic->get_Tc()[0] / cubic->get_pc()[0] * 1000) + 0.00773144012514; // [L/mol]
170 return 1 / (v_c_Lmol / 1000.0);
171 } else {
173 }
174 };
179 const std::vector<double>& pc = cubic->get_pc();
180 if (pc.empty()) {
181 throw ValueError("CubicBackend::calc_pmax: Critical pressure vector is empty. Missing cubic parameters.");
182 }
183 double pc_max = *std::max_element(pc.begin(), pc.end());
184 return 100.0 * pc_max;
185 };
190 const std::vector<double>& pc = cubic->get_pc();
191 if (pc.empty()) {
192 throw ValueError("CubicBackend::calc_p_triple: Critical pressure vector is empty. Missing cubic parameters.");
193 }
194 double pc_min = *std::min_element(pc.begin(), pc.end());
195 return 0.01 * pc_min;
196 };
201 const std::vector<double>& Tc = cubic->get_Tc();
202 if (Tc.empty()) {
203 throw ValueError("CubicBackend::calc_Tmin: Critical temperature vector is empty. Missing cubic parameters.");
204 }
205 double Tc_min = *std::min_element(Tc.begin(), Tc.end());
206 return 0.3 * Tc_min;
207 };
212 const std::vector<double>& Tc = cubic->get_Tc();
213 if (Tc.empty()) {
214 throw ValueError("CubicBackend::calc_Tmax: Critical temperature vector is empty. Missing cubic parameters.");
215 }
216 double Tc_max = *std::max_element(Tc.begin(), Tc.end());
217 return 10.0 * Tc_max;
218 };
219
222 void get_linear_reducing_parameters(double& rhomolar, double& T);
223
225 void get_critical_point_starting_values(double& delta0, double& tau0) override;
226
228 void get_critical_point_search_radii(double& R_delta, double& R_tau) override;
229
231 bool get_critical_is_terminated(double& delta, double& tau) override;
232
233 CoolPropDbl calc_alphar_deriv_nocache(const int nTau, const int nDelta, const std::vector<CoolPropDbl>& mole_fractions, const CoolPropDbl& tau,
234 const CoolPropDbl& delta) override;
235
238
240 virtual void update_DmolarT();
241
242 void update(CoolProp::input_pairs input_pair, double value1, double value2) override;
243
266 void rho_Tp_cubic(CoolPropDbl T, CoolPropDbl p, int& Nsolns, double& rho0, double& rho1, double& rho2);
267
276 // Catch only the expected "no root" CoolProp error (a ValueError, which
277 // derives from CoolPropBaseError) -- let std::bad_alloc / programming
278 // errors propagate rather than silently degrading them to a -1 sentinel.
279 try {
280 return solver_rho_Tp(T, p, phase);
281 } catch (const CoolPropBaseError&) {
282 return -1;
283 }
284 };
291
294
296
299 TPD_state.reset(get_copy());
300 };
301
304
305 CoolPropDbl calc_molar_mass() override;
306
307 void set_binary_interaction_double(const std::size_t i1, const std::size_t i2, const std::string& parameter, const double value) override;
308 double get_binary_interaction_double(const std::size_t i1, const std::size_t i2, const std::string& parameter) override;
309
310 void set_binary_interaction_double(const std::string& CAS1, const std::string& CAS2, const std::string& parameter, const double value) override {
311 throw ValueError("set_binary_interaction_double not defined for AbstractCubic not defined for CAS #");
312 }
313 double get_binary_interaction_double(const std::string& CAS1, const std::string& CAS2, const std::string& parameter) override {
314 throw ValueError("get_binary_interaction_double not defined for AbstractCubic not defined for CAS #");
315 };
316
317 // Return a 1-1 copy of this class
318 HelmholtzEOSMixtureBackend* get_copy(bool generate_SatL_and_SatV = true) override = 0;
319
320 // Copy the entire kij matrix from another instance in one shot
321 void copy_k(AbstractCubicBackend* donor);
322
323 //
325
328
329 // Set the cubic alpha function's constants:
330 void set_cubic_alpha_C(const size_t i, const std::string& parameter, const double c1, const double c2, const double c3) override;
331
332 // Set fluid parameter (currently the volume translation parameter)
333 void set_fluid_parameter_double(const size_t i, const std::string& parameter, const double value) override;
334
335 // Get fluid parameter (currently the volume translation parameter)
336 double get_fluid_parameter_double(const size_t i, const std::string& parameter) override;
337
340 virtual int get_superanc_eos_code() const {
342 }
343
344 CoolPropDbl calc_saturation_ancillary(parameters param, int Q, parameters given, double value) override;
345
347
350 double calc_superanc_Tmax();
351};
352
354{
355
356 public:
357 SRKBackend(const std::vector<double>& Tc, const std::vector<double>& pc, const std::vector<double>& acentric, double R_u,
358 bool generate_SatL_and_SatV = true) {
359 cubic = std::make_shared<SRK>(Tc, pc, acentric, R_u);
360 setup(generate_SatL_and_SatV);
361 };
362 SRKBackend(double Tc, double pc, double acentric, double R_u, bool generate_SatL_and_SatV = true) {
363 cubic = std::make_shared<SRK>(Tc, pc, acentric, R_u);
364 setup(generate_SatL_and_SatV);
365 }
366 SRKBackend(const std::vector<std::string>& fluid_identifiers, const double R_u = get_config_double(R_U_CODATA),
367 bool generate_SatL_and_SatV = true) {
368 std::vector<double> Tc, pc, acentric;
369 N = fluid_identifiers.size();
370 components.resize(N);
371 for (std::size_t i = 0; i < fluid_identifiers.size(); ++i) {
372 components[i] = CubicLibrary::get_cubic_values(fluid_identifiers[i]);
373 Tc.push_back(components[i].Tc);
374 pc.push_back(components[i].pc);
375 acentric.push_back(components[i].acentric);
376 }
377 cubic = std::make_shared<SRK>(Tc, pc, acentric, R_u);
378 setup(generate_SatL_and_SatV);
379 }
380 HelmholtzEOSMixtureBackend* get_copy(bool generate_SatL_and_SatV = true) override {
381 AbstractCubicBackend* ACB = new SRKBackend(cubic->get_Tc(), cubic->get_pc(), cubic->get_acentric(), cubic->get_R_u(), generate_SatL_and_SatV);
382 ACB->copy_internals(*this);
383 return static_cast<HelmholtzEOSMixtureBackend*>(ACB);
384 }
385 std::string backend_name() override {
387 }
388 int get_superanc_eos_code() const override {
390 }
391};
392
394{
395
396 public:
397 PengRobinsonBackend() = default; // Default constructor (make sure you know what you are doing)
398 PengRobinsonBackend(const std::vector<double>& Tc, const std::vector<double>& pc, const std::vector<double>& acentric, double R_u,
399 bool generate_SatL_and_SatV = true) {
400 cubic = std::make_shared<PengRobinson>(Tc, pc, acentric, R_u);
401 setup(generate_SatL_and_SatV);
402 };
403 PengRobinsonBackend(double Tc, double pc, double acentric, double R_u, bool generate_SatL_and_SatV = true) {
404 cubic = std::make_shared<PengRobinson>(Tc, pc, acentric, R_u);
405 setup(generate_SatL_and_SatV);
406 };
407 PengRobinsonBackend(const std::vector<std::string>& fluid_identifiers, const double R_u = get_config_double(R_U_CODATA),
408 bool generate_SatL_and_SatV = true) {
409 std::vector<double> Tc, pc, acentric;
410 N = fluid_identifiers.size();
411 components.resize(N);
412 for (std::size_t i = 0; i < fluid_identifiers.size(); ++i) {
413 components[i] = CubicLibrary::get_cubic_values(fluid_identifiers[i]);
414 Tc.push_back(components[i].Tc);
415 pc.push_back(components[i].pc);
416 acentric.push_back(components[i].acentric);
417 }
418 cubic = std::make_shared<PengRobinson>(Tc, pc, acentric, R_u);
419 setup(generate_SatL_and_SatV);
420 };
421 HelmholtzEOSMixtureBackend* get_copy(bool generate_SatL_and_SatV = true) override {
423 new PengRobinsonBackend(cubic->get_Tc(), cubic->get_pc(), cubic->get_acentric(), cubic->get_R_u(), generate_SatL_and_SatV);
424 ACB->copy_internals(*this);
425 return static_cast<HelmholtzEOSMixtureBackend*>(ACB);
426 }
427 std::string backend_name() override {
429 }
430 int get_superanc_eos_code() const override {
432 }
433};
434
440{
441
442 protected:
444
445 public:
447 : ACB(nullptr) {
448
449 };
451
452 // copy assignment
454 ACB = other.ACB;
455 return *this;
456 }
457
459 HelmholtzDerivatives all(HelmholtzEOSMixtureBackend& HEOS, const std::vector<CoolPropDbl>& mole_fractions, double tau, double delta,
460 bool cache_values = false) override {
462 // The cubic mixing functions take a std::vector<double>; mole_fractions is a
463 // std::vector<CoolPropDbl>. When CoolPropDbl == double (the default config) the two types
464 // are identical, so bind a const reference and skip the per-call heap allocation in the
465 // residual hot path; the long-double config still needs the converting copy.
466#ifdef COOLPROPDBL_MAPS_TO_DOUBLE
467 const std::vector<double>& z = mole_fractions;
468#else
469 std::vector<double> z = std::vector<double>(mole_fractions.begin(), mole_fractions.end());
470#endif
471 shared_ptr<AbstractCubic>& cubic = ACB->get_cubic();
472
473 // AbstractCubic::alphar(itau, idelta) is
474 // psi_minus(delta, itau, idelta) - tau_times_a(tau, itau)/(R_u*T_r) * psi_plus(delta, idelta),
475 // and psi_minus is identically zero for itau > 0. So all 15 derivatives factor over three
476 // small sets of intermediates, each filled below at every derivative order n in [0, 4] and
477 // indexed by the order it carries -- note n means a delta-derivative for the two psi arrays
478 // but a tau-derivative for tau_times_a:
479 // psi_minus[n] = psi_minus(delta, itau=0, idelta=n) -- n-th delta-derivative of psi_minus
480 // psi_plus[n] = psi_plus(delta, idelta=n) -- n-th delta-derivative of psi_plus
481 // tau_times_a[n] = tau_times_a(tau, itau=n) -- n-th tau-derivative of tau*a
482 // The 15 separate alphar() calls would otherwise each rebuild these from scratch (the
483 // am/bm/psi mixing terms dominated the residual hot path). Compute each once, then assemble.
484 // The assembly is term-for-term identical to alphar() (same operands, same grouping, with the
485 // literal 0.0 standing in for psi_minus at itau > 0), so results are bit-for-bit unchanged.
486 std::array<double, 5> psi_minus{}, psi_plus{}, tau_times_a{};
487 for (std::size_t n = 0; n <= 4; ++n) {
488 psi_minus[n] = cubic->psi_minus(delta, z, 0, n);
489 psi_plus[n] = cubic->psi_plus(delta, z, n);
490 tau_times_a[n] = cubic->tau_times_a(tau, z, n);
491 }
492 const double den = cubic->get_R_u() * cubic->get_Tr();
493 a.alphar = psi_minus[0] - tau_times_a[0] / den * psi_plus[0];
494 a.dalphar_dtau = 0.0 - tau_times_a[1] / den * psi_plus[0];
495 a.dalphar_ddelta = psi_minus[1] - tau_times_a[0] / den * psi_plus[1];
496 a.d2alphar_dtau2 = 0.0 - tau_times_a[2] / den * psi_plus[0];
497 a.d2alphar_ddelta_dtau = 0.0 - tau_times_a[1] / den * psi_plus[1];
498 a.d2alphar_ddelta2 = psi_minus[2] - tau_times_a[0] / den * psi_plus[2];
499 a.d3alphar_dtau3 = 0.0 - tau_times_a[3] / den * psi_plus[0];
500 a.d3alphar_ddelta_dtau2 = 0.0 - tau_times_a[2] / den * psi_plus[1];
501 a.d3alphar_ddelta2_dtau = 0.0 - tau_times_a[1] / den * psi_plus[2];
502 a.d3alphar_ddelta3 = psi_minus[3] - tau_times_a[0] / den * psi_plus[3];
503 a.d4alphar_dtau4 = 0.0 - tau_times_a[4] / den * psi_plus[0];
504 a.d4alphar_ddelta_dtau3 = 0.0 - tau_times_a[3] / den * psi_plus[1];
505 a.d4alphar_ddelta2_dtau2 = 0.0 - tau_times_a[2] / den * psi_plus[2];
506 a.d4alphar_ddelta3_dtau = 0.0 - tau_times_a[1] / den * psi_plus[3];
507 a.d4alphar_ddelta4 = psi_minus[4] - tau_times_a[0] / den * psi_plus[4];
508 return a;
509 }
511 return ACB->get_cubic()->d_alphar_dxi(HEOS.tau(), HEOS.delta(), HEOS.get_mole_fractions_doubleref(), 0, 0, i, xN_flag == XN_INDEPENDENT);
512 }
514 return ACB->get_cubic()->d_alphar_dxi(HEOS.tau(), HEOS.delta(), HEOS.get_mole_fractions_doubleref(), 1, 0, i, xN_flag == XN_INDEPENDENT);
515 }
517 return ACB->get_cubic()->d_alphar_dxi(HEOS.tau(), HEOS.delta(), HEOS.get_mole_fractions_doubleref(), 0, 1, i, xN_flag == XN_INDEPENDENT);
518 }
520 return ACB->get_cubic()->d_alphar_dxi(HEOS.tau(), HEOS.delta(), HEOS.get_mole_fractions_doubleref(), 2, 0, i, xN_flag == XN_INDEPENDENT);
521 }
523 return ACB->get_cubic()->d_alphar_dxi(HEOS.tau(), HEOS.delta(), HEOS.get_mole_fractions_doubleref(), 1, 1, i, xN_flag == XN_INDEPENDENT);
524 }
526 return ACB->get_cubic()->d_alphar_dxi(HEOS.tau(), HEOS.delta(), HEOS.get_mole_fractions_doubleref(), 0, 2, i, xN_flag == XN_INDEPENDENT);
527 }
528
529 CoolPropDbl d2alphardxidxj(HelmholtzEOSMixtureBackend& HEOS, std::size_t i, std::size_t j, x_N_dependency_flag xN_flag) override {
530 return ACB->get_cubic()->d2_alphar_dxidxj(HEOS.tau(), HEOS.delta(), HEOS.get_mole_fractions_doubleref(), 0, 0, i, j,
531 xN_flag == XN_INDEPENDENT);
532 }
533 CoolPropDbl d3alphar_dxi_dxj_dTau(HelmholtzEOSMixtureBackend& HEOS, std::size_t i, std::size_t j, x_N_dependency_flag xN_flag) override {
534 return ACB->get_cubic()->d2_alphar_dxidxj(HEOS.tau(), HEOS.delta(), HEOS.get_mole_fractions_doubleref(), 1, 0, i, j,
535 xN_flag == XN_INDEPENDENT);
536 }
537 CoolPropDbl d3alphar_dxi_dxj_dDelta(HelmholtzEOSMixtureBackend& HEOS, std::size_t i, std::size_t j, x_N_dependency_flag xN_flag) override {
538 return ACB->get_cubic()->d2_alphar_dxidxj(HEOS.tau(), HEOS.delta(), HEOS.get_mole_fractions_doubleref(), 0, 1, i, j,
539 xN_flag == XN_INDEPENDENT);
540 }
541
543 return ACB->get_cubic()->d_alphar_dxi(HEOS.tau(), HEOS.delta(), HEOS.get_mole_fractions_doubleref(), 3, 0, i, xN_flag == XN_INDEPENDENT);
544 }
546 return ACB->get_cubic()->d_alphar_dxi(HEOS.tau(), HEOS.delta(), HEOS.get_mole_fractions_doubleref(), 1, 2, i, xN_flag == XN_INDEPENDENT);
547 }
549 return ACB->get_cubic()->d_alphar_dxi(HEOS.tau(), HEOS.delta(), HEOS.get_mole_fractions_doubleref(), 2, 1, i, xN_flag == XN_INDEPENDENT);
550 }
552 return ACB->get_cubic()->d_alphar_dxi(HEOS.tau(), HEOS.delta(), HEOS.get_mole_fractions_doubleref(), 0, 3, i, xN_flag == XN_INDEPENDENT);
553 }
554 CoolPropDbl d4alphar_dxi_dxj_dTau2(HelmholtzEOSMixtureBackend& HEOS, std::size_t i, std::size_t j, x_N_dependency_flag xN_flag) override {
555 return ACB->get_cubic()->d2_alphar_dxidxj(HEOS.tau(), HEOS.delta(), HEOS.get_mole_fractions_doubleref(), 2, 0, i, j,
556 xN_flag == XN_INDEPENDENT);
557 }
558 CoolPropDbl d4alphar_dxi_dxj_dDelta_dTau(HelmholtzEOSMixtureBackend& HEOS, std::size_t i, std::size_t j, x_N_dependency_flag xN_flag) override {
559 return ACB->get_cubic()->d2_alphar_dxidxj(HEOS.tau(), HEOS.delta(), HEOS.get_mole_fractions_doubleref(), 1, 1, i, j,
560 xN_flag == XN_INDEPENDENT);
561 }
562 CoolPropDbl d4alphar_dxi_dxj_dDelta2(HelmholtzEOSMixtureBackend& HEOS, std::size_t i, std::size_t j, x_N_dependency_flag xN_flag) override {
563 return ACB->get_cubic()->d2_alphar_dxidxj(HEOS.tau(), HEOS.delta(), HEOS.get_mole_fractions_doubleref(), 0, 2, i, j,
564 xN_flag == XN_INDEPENDENT);
565 }
566 CoolPropDbl d3alphardxidxjdxk(HelmholtzEOSMixtureBackend& HEOS, std::size_t i, std::size_t j, std::size_t k,
567 x_N_dependency_flag xN_flag) override {
568 return ACB->get_cubic()->d3_alphar_dxidxjdxk(HEOS.tau(), HEOS.delta(), HEOS.get_mole_fractions_doubleref(), 0, 0, i, j, k,
569 xN_flag == XN_INDEPENDENT);
570 }
571 CoolPropDbl d4alphar_dxi_dxj_dxk_dTau(HelmholtzEOSMixtureBackend& HEOS, std::size_t i, std::size_t j, std::size_t k,
572 x_N_dependency_flag xN_flag) override {
573 return ACB->get_cubic()->d3_alphar_dxidxjdxk(HEOS.tau(), HEOS.delta(), HEOS.get_mole_fractions_doubleref(), 1, 0, i, j, k,
574 xN_flag == XN_INDEPENDENT);
575 }
576 CoolPropDbl d4alphar_dxi_dxj_dxk_dDelta(HelmholtzEOSMixtureBackend& HEOS, std::size_t i, std::size_t j, std::size_t k,
577 x_N_dependency_flag xN_flag) override {
578 return ACB->get_cubic()->d3_alphar_dxidxjdxk(HEOS.tau(), HEOS.delta(), HEOS.get_mole_fractions_doubleref(), 0, 1, i, j, k,
579 xN_flag == XN_INDEPENDENT);
580 }
581};
582
583} /* namespace CoolProp */
584#endif /* CUBICBACKEND_H_ */