CoolProp 8.0.0
An open-source fluid property and humid air property database
VLERoutines.h
Go to the documentation of this file.
1#ifndef VLEROUTINES_H
2#define VLEROUTINES_H
3
5
6namespace CoolProp {
7
8namespace SaturationSolvers {
10{
14 : use_guesses(use_guesses), omega(_HUGE), rhoL(_HUGE), rhoV(_HUGE), pL(_HUGE), pV(_HUGE) {}
15};
17{
19 saturation_T_pure_options() : omega(_HUGE), rhoL(_HUGE), rhoV(_HUGE), pL(_HUGE), pV(_HUGE), p(_HUGE), T(_HUGE) {}
20};
21
23{
25 {
28 };
35 : use_guesses(false),
36 use_logdelta(true),
37 omega(1.0),
38 rhoL(_HUGE),
39 rhoV(_HUGE),
40 pL(_HUGE),
41 pV(_HUGE),
42 imposed_rho(0),
43 max_iterations(200) {} // Defaults
44};
45
47{
50};
52{
56 std::vector<CoolPropDbl> x, y, K;
57};
58
68static CoolPropDbl Wilson_lnK_factor(const HelmholtzEOSMixtureBackend& HEOS, CoolPropDbl T, CoolPropDbl p, std::size_t i) {
69 const double pci = HEOS.get_fluid_constant(i, iP_critical);
70 const double Tci = HEOS.get_fluid_constant(i, iT_critical);
71 const double omegai = HEOS.get_fluid_constant(i, iacentric_factor);
72 return log(pci / p) + 5.373 * (1 + omegai) * (1 - Tci / T);
73};
74
75void saturation_D_pure(HelmholtzEOSMixtureBackend& HEOS, CoolPropDbl rhomolar, saturation_D_pure_options& options);
76void saturation_T_pure(HelmholtzEOSMixtureBackend& HEOS, CoolPropDbl T, saturation_T_pure_options& options);
77void saturation_T_pure_Akasaka(HelmholtzEOSMixtureBackend& HEOS, CoolPropDbl T, saturation_T_pure_Akasaka_options& options);
78void saturation_T_pure_Maxwell(HelmholtzEOSMixtureBackend& HEOS, CoolPropDbl T, saturation_T_pure_Akasaka_options& options);
79
81{
83 {
93 };
99 : use_guesses(true),
100 use_logdelta(true),
102 omega(1.0),
103 rhoL(_HUGE),
104 rhoV(_HUGE),
105 pL(_HUGE),
106 pV(_HUGE),
107 T(_HUGE),
108 p(_HUGE) {}
109};
110
111#if !defined(NO_FMTLIB) && FMT_VERSION >= 90000
113 return fmt::underlying(option);
114}
115#endif
116
117void saturation_PHSU_pure(HelmholtzEOSMixtureBackend& HEOS, CoolPropDbl specified_value, saturation_PHSU_pure_options& options);
118
119/* \brief This is a backup saturation_p solver for the case where the Newton solver cannot approach closely enough the solution
120 *
121 * This is especially a problem at low pressures where catastrophic truncation error occurs, especially in the saturated vapor side
122 *
123 * @param HEOS The Helmholtz EOS backend instance to be used
124 * @param p Imposed pressure in kPa
125 * @param options Options to be passed to the function (at least T, rhoL and rhoV must be provided)
126 */
127void saturation_P_pure_1D_T(HelmholtzEOSMixtureBackend& HEOS, CoolPropDbl p, saturation_PHSU_pure_options& options);
128
129/* \brief This is a backup saturation_T solver for the case where the Newton solver cannot approach closely enough the solution
130 *
131 * This is especially a problem at low pressures where catastrophic truncation error occurs, especially in the saturated vapor side
132 *
133 * @param HEOS The Helmholtz EOS backend instance to be used
134 * @param T Imposed temperature in K
135 * @param options Options to be passed to the function (at least p, rhoL and rhoV must be provided)
136 */
137void saturation_T_pure_1D_P(HelmholtzEOSMixtureBackend& HEOS, CoolPropDbl T, saturation_T_pure_options& options);
138
139/* \brief A robust but slow solver in the very-near-critical region
140 *
141 * This solver operates in the following fashion:
142 * 1. Using a bounded interval for rho'':[rhoc, rhoc-??], guess a value for rho''
143 * 2. For guessed value of rho'' and given value of T, calculate p
144 * 3. Using a Brent solver on the other co-existing phase (rho'), calculate the (bounded) value of rho' that yields the same pressure
145 * 4. Use another outer Brent solver on rho'' to enforce the same Gibbs function between liquid and vapor
146 * 5. Fin.
147 *
148 * @param HEOS The Helmholtz EOS backend instance to be used
149 * @param ykey The CoolProp::parameters key to be imposed - one of iT or iP
150 * @param y The value for the imposed variable
151 */
152void saturation_critical(HelmholtzEOSMixtureBackend& HEOS, CoolProp::parameters ykey, CoolPropDbl y);
153
154void successive_substitution(HelmholtzEOSMixtureBackend& HEOS, const CoolPropDbl beta, CoolPropDbl T, CoolPropDbl p,
155 const std::vector<CoolPropDbl>& z, std::vector<CoolPropDbl>& K, mixture_VLE_IO& options);
163void x_and_y_from_K(CoolPropDbl beta, const std::vector<CoolPropDbl>& K, const std::vector<CoolPropDbl>& z, std::vector<CoolPropDbl>& x,
164 std::vector<CoolPropDbl>& y);
165
175void successive_substitution_guessrho(HelmholtzEOSMixtureBackend& HEOS, std::vector<CoolPropDbl>& x, std::vector<CoolPropDbl>& y,
176 CoolPropDbl& rhomolar_liq, CoolPropDbl& rhomolar_vap, const std::vector<CoolPropDbl>& z, int num_steps,
177 double tol = 1e-6);
178
188bool guess_split_from_wilson(HelmholtzEOSMixtureBackend& HEOS, std::vector<CoolPropDbl>& x, std::vector<CoolPropDbl>& y, CoolPropDbl& rhomolar_liq,
189 CoolPropDbl& rhomolar_vap, const std::vector<CoolPropDbl>& z, CoolPropDbl T, CoolPropDbl p, int num_steps,
190 bool require_bracket = true);
191
198{
199 public:
201 double T, p, beta;
202 const std::vector<CoolPropDbl>& z;
203 std::vector<CoolPropDbl>& K;
205
207 const std::vector<CoolPropDbl>& z, std::vector<CoolPropDbl>& K)
209 T(imposed_value),
210 p(imposed_value),
211 beta(beta),
212 z(z),
213 K(K),
214 HEOS(HEOS) {} // if input_type == imposed_T -> use T, else use p; init both
215 double call(double input_value) override {
216 double summer = 0;
217 if (input_type == imposed_T) {
218 p = input_value; // Iterate on pressure
219 } else {
220 T = input_value; // Iterate on temperature, pressure imposed
221 }
222 for (unsigned int i = 0; i < z.size(); i++) {
223 K[i] = exp(Wilson_lnK_factor(HEOS, T, p, i));
224 summer += z[i] * (K[i] - 1) / (1 - beta + beta * K[i]);
225 }
226 return summer;
227 }
228};
229inline double saturation_preconditioner(HelmholtzEOSMixtureBackend& HEOS, double input_value, sstype_enum input_type,
230 const std::vector<CoolPropDbl>& z) {
231 double ptriple = 0, pcrit = 0, Ttriple = 0, Tcrit = 0;
232
233 if (HEOS.get_components().empty()) {
234 return -1;
235 }
236
237 for (unsigned int i = 0; i < z.size(); i++) {
238 Tcrit += HEOS.get_fluid_constant(i, iT_critical) * z[i];
239 pcrit += HEOS.get_fluid_constant(i, iP_critical) * z[i];
240 Ttriple += HEOS.get_fluid_constant(i, iT_triple) * z[i];
241 ptriple += HEOS.get_fluid_constant(i, iP_triple) * z[i];
242 }
243 // Return an invalid number if either triple point temperature or pressure are not available
244 if (!ValidNumber(Ttriple) || !ValidNumber(ptriple)) {
245 return _HUGE;
246 }
247
248 if (input_type == imposed_T) {
249 return exp(log(pcrit / ptriple) / (Tcrit - Ttriple) * (input_value - Ttriple) + log(ptriple));
250 } else if (input_type == imposed_p) {
251 return 1 / (1 / Tcrit - (1 / Ttriple - 1 / Tcrit) / log(pcrit / ptriple) * log(input_value / pcrit));
252 } else {
253 throw ValueError();
254 }
255}
283inline double saturation_Wilson(HelmholtzEOSMixtureBackend& HEOS, double beta, double input_value, sstype_enum input_type,
284 const std::vector<CoolPropDbl>& z, double guess) {
285 double out = 0;
286 std::string errstr;
287
288 // If T is input and beta = 0 or beta = 1, explicit solution for p is possible
289 if (input_type == imposed_T && (std::abs(beta) < 1e-12 || std::abs(beta - 1) < 1e-12)) {
290 const std::vector<double> z = HEOS.get_mole_fractions_ref();
291 bool beta0 = std::abs(beta) < 1e-12; // True is beta is approx. zero
292 for (int i = 0; i < static_cast<int>(z.size()); ++i) {
293 double pci = HEOS.get_fluid_constant(i, iP_critical);
294 double Tci = HEOS.get_fluid_constant(i, iT_critical);
295 double omegai = HEOS.get_fluid_constant(i, iacentric_factor);
296 if (beta0) {
297 out += z[i] * pci * exp(5.373 * (1 + omegai) * (1 - Tci / input_value));
298 } else {
299 out += z[i] / (pci * exp(5.373 * (1 + omegai) * (1 - Tci / input_value)));
300 }
301 }
302 if (!beta0) { // beta = 1
303 out = 1 / out; // summation is for 1/p, take reciprocal to get p
304 }
305 std::vector<CoolPropDbl>& K = HEOS.get_K();
306 for (int i = 0; i < static_cast<int>(z.size()); ++i) {
307 double pci = HEOS.get_fluid_constant(i, iP_critical);
308 double Tci = HEOS.get_fluid_constant(i, iT_critical);
309 double omegai = HEOS.get_fluid_constant(i, iacentric_factor);
310 K[i] = pci / out * exp(5.373 * (1 + omegai) * (1 - Tci / input_value));
311 }
312 } else {
313 // Find first guess for output variable using Wilson K-factors.
314 // Always try Brent (bounded) first — Secant (unbounded) can diverge
315 // for multi-component mixtures at certain Q values.
316 // Fix from jakobreichert PR CoolProp/CoolProp#2720.
317 WilsonK_resid Resid(HEOS, beta, input_value, input_type, z, HEOS.get_K());
318 try {
319 if (input_type == imposed_T) {
320 out = Brent(Resid, 1.0, 1e9, 1e-10, 1e-10, 100);
321 } else {
322 out = Brent(Resid, 50, 10000, 1e-10, 1e-10, 100);
323 }
324 } catch (const std::exception&) {
325 // Brent failed to bracket; fall back to Secant from the provided guess
326 if (!ValidNumber(guess) || guess < 0) {
327 throw;
328 }
329 out = Secant(Resid, guess, 0.001, 1e-10, 100);
330 }
331 if (!ValidNumber(out)) {
332 throw ValueError("saturation_p_Wilson failed to get good output value");
333 }
334 }
335 return out;
336}
338{
340};
341
343{
345 {
349 };
351 std::size_t Nsteps;
354 std::vector<CoolPropDbl> x, y, z;
356 : Nstep_max(30),
357 Nsteps(0),
358 beta(-1),
359 omega(1),
360 rhomolar_liq(_HUGE),
361 rhomolar_vap(_HUGE),
362 pL(_HUGE),
363 pV(_HUGE),
364 p(_HUGE),
365 T(_HUGE),
366 hmolar_liq(_HUGE),
367 hmolar_vap(_HUGE),
368 smolar_liq(_HUGE),
369 smolar_vap(_HUGE),
371};
372
404{
405 public:
409 std::size_t N;
412 Eigen::MatrixXd J;
413 // r and err_rel hold 2N-1 residuals/relative-errors (see class docstring), so they MUST
414 // be dynamically sized. They were Eigen::Vector2d (fixed size 2), which overflowed for
415 // every mixture (N>=2 -> 2N-1>=3) and hard-crashed via out-of-bounds writes (GH #3192).
416 Eigen::VectorXd r, err_rel;
417 std::vector<CoolPropDbl> K, x, y, z;
418 std::vector<SuccessiveSubstitutionStep> step_logger;
419
421 : HEOS(nullptr),
423 error_rms(_HUGE),
424 rhomolar_liq(_HUGE),
425 rhomolar_vap(_HUGE),
426 T(_HUGE),
427 p(_HUGE),
428 min_rel_change(_HUGE),
429 beta(_HUGE),
430 N(0),
431 logging(false),
432 Nsteps(0) {};
433
434 void resize(unsigned int N);
435
436 // Reset the state of all the internal variables
437 void pre_call() {
438 K.clear();
439 x.clear();
440 y.clear();
441 step_logger.clear();
442 error_rms = 1e99;
443 Nsteps = 0;
444 rhomolar_liq = _HUGE;
445 rhomolar_vap = _HUGE;
446 T = _HUGE;
447 p = _HUGE;
448 };
449
460
461 /* \brief Build the arrays for the Newton-Raphson solve
462 *
463 */
464 void build_arrays();
465};
466
468{
470 {
475 };
478 std::size_t Nsteps;
481 std::vector<CoolPropDbl> x, y;
483 : Nstep_max(30),
484 bubble_point(false),
485 Nsteps(0),
486 omega(_HUGE),
487 rhomolar_liq(_HUGE),
488 rhomolar_vap(_HUGE),
489 pL(_HUGE),
490 pV(_HUGE),
491 p(_HUGE),
492 T(_HUGE),
493 hmolar_liq(_HUGE),
494 hmolar_vap(_HUGE),
495 smolar_liq(_HUGE),
496 smolar_vap(_HUGE),
498};
499
528{
529 public:
531 CoolPropDbl error_rms = _HUGE, rhomolar_liq = _HUGE, rhomolar_vap = _HUGE, T = _HUGE, p = _HUGE, min_rel_change = _HUGE;
532 std::size_t N = 0;
533 bool logging = false;
534 bool bubble_point = false;
535 int Nsteps = 0;
536 Eigen::MatrixXd J;
539 std::vector<CoolPropDbl> K, x, y;
540 Eigen::VectorXd r, err_rel;
541 std::vector<SuccessiveSubstitutionStep> step_logger;
542
544
545 void resize(std::size_t N);
546
547 // Reset the state of all the internal variables
548 void pre_call() {
549 step_logger.clear();
550 error_rms = 1e99;
551 Nsteps = 0;
552 rhomolar_liq = _HUGE;
553 rhomolar_vap = _HUGE;
554 T = _HUGE;
555 p = _HUGE;
556 };
557
569 void call(HelmholtzEOSMixtureBackend& HEOS, const std::vector<CoolPropDbl>& z, std::vector<CoolPropDbl>& z_incipient,
571
577 void build_arrays();
578
581 void check_Jacobian();
582};
583
588{
590 std::size_t Nsteps;
593 std::vector<CoolPropDbl> x,
603 : Nstep_max(30),
604 Nsteps(0),
605 omega(_HUGE),
606 rhomolar_liq(_HUGE),
607 rhomolar_vap(_HUGE),
608 pL(_HUGE),
609 pV(_HUGE),
610 p(_HUGE),
611 T(_HUGE),
612 beta(0.5),
613 nonconvergence(false) {}
614};
615
625{
626 public:
627 double error_rms = _HUGE;
628 bool logging = false;
629 int Nsteps = 0;
630 Eigen::MatrixXd J;
631 Eigen::VectorXd r, err_rel;
634 std::vector<SuccessiveSubstitutionStep> step_logger;
635
637
645 void solve();
646 void solve_legacy();
647 void solve_michelsen();
648
654 void build_arrays();
655};
656}; // namespace SaturationSolvers
657
658namespace StabilityRoutines {
659
667{
668 protected:
670 std::vector<double> lnK, K, K0, x, y, xL, xH;
671 const std::vector<double>& z;
673 double m_T,
675
676 private:
677 bool _stable;
678 bool _uncertain;
679 bool debug;
680 bool use_michelsen;
681
682 public:
684 : HEOS(HEOS),
685 z(HEOS.get_mole_fractions_doubleref()),
686 rhomolar_liq(-1),
687 rhomolar_vap(-1),
688 beta(-1),
689 tpd_liq(10000),
690 tpd_vap(100000),
691 DELTAG_nRT(10000),
692 m_T(-1),
693 m_p(-1),
694 _stable(false),
695 _uncertain(false),
696 debug(false),
697 use_michelsen(get_config_int(MIXTURE_STABILITY_ALGORITHM) != 0) {};
700 void set_TP(double T, double p) {
701 m_T = T;
702 m_p = p;
703 };
706 void rho_TP_w_guesses();
709 void rho_TP_global();
713
716 void trial_compositions();
719 void successive_substitution(int num_steps);
720
725 void check_stability();
730
743 bool minimize_tpd(std::vector<CoolPropDbl>& Y, const std::vector<CoolPropDbl>& ln_f_z, CoolPropDbl the_T, CoolPropDbl the_p, bool& is_unstable);
744
746 void set_use_michelsen(bool value) {
747 use_michelsen = value;
748 }
749
752 bool is_stable() {
753 if (!use_michelsen) {
754 // Legacy path needs trial compositions and a few SS steps
755 // before check_stability_legacy() can evaluate TPD
758 }
760 return _stable;
761 }
765 bool is_uncertain() const {
766 return _uncertain;
767 }
769 void get_liq(std::vector<double>& x, double& rhomolar) {
770 x = this->x;
771 rhomolar = rhomolar_liq;
772 }
774 void get_vap(std::vector<double>& y, double& rhomolar) {
775 y = this->y;
776 rhomolar = rhomolar_vap;
777 }
778};
779
780}; /* namespace StabilityRoutines*/
781
782}; /* namespace CoolProp*/
783
784#endif