CoolProp 7.0.0
An open-source fluid property and humid air property database
FlashRoutines.cpp
Go to the documentation of this file.
1#include "VLERoutines.h"
2#include "FlashRoutines.h"
6#include "Configuration.h"
7
8#if defined(ENABLE_CATCH)
9# include <catch2/catch_all.hpp>
11#endif
12
13namespace CoolProp {
14
16 if (HEOS.PhaseEnvelope.built) {
17 // Use the phase envelope if already constructed to determine phase boundary
18 // Determine whether you are inside (two-phase) or outside (single-phase)
19 SimpleState closest_state;
20 std::size_t i;
21 bool twophase = PhaseEnvelopeRoutines::is_inside(HEOS.PhaseEnvelope, iP, HEOS._p, iT, HEOS._T, i, closest_state);
22 if (!twophase && HEOS._T > closest_state.T) {
23 // Gas solution - bounded between phase envelope temperature and very high temperature
24 //
25 // Start with a guess value from SRK
26 CoolPropDbl rhomolar_guess = HEOS.solver_rho_Tp_SRK(HEOS._T, HEOS._p, iphase_gas);
27
28 solver_TP_resid resid(HEOS, HEOS._T, HEOS._p);
29 std::string errstr;
31 try {
32 // Try using Newton's method
33 CoolPropDbl rhomolar = Newton(resid, rhomolar_guess, 1e-10, 100);
34 // Make sure the solution is within the bounds
35 if (!is_in_closed_range(static_cast<CoolPropDbl>(closest_state.rhomolar), static_cast<CoolPropDbl>(0.0), rhomolar)) {
36 throw ValueError("out of range");
37 }
38 HEOS.update_DmolarT_direct(rhomolar, HEOS._T);
39 } catch (...) {
40 // If that fails, try a bounded solver
41 CoolPropDbl rhomolar = Brent(resid, closest_state.rhomolar, 1e-10, DBL_EPSILON, 1e-10, 100);
42 // Make sure the solution is within the bounds
43 if (!is_in_closed_range(static_cast<CoolPropDbl>(closest_state.rhomolar), static_cast<CoolPropDbl>(0.0), rhomolar)) {
44 throw ValueError("out of range");
45 }
46 }
47 HEOS.unspecify_phase();
48 HEOS._Q = -1;
49 } else {
50 // Liquid solution
51 throw ValueError();
52 }
53 } else {
55 // Blind flash call
56 // Following the strategy of Gernert, 2014
58 if (!stability_tester.is_stable()) {
59 // There is a phase split and liquid and vapor phases are formed
60 CoolProp::SaturationSolvers::PTflash_twophase_options o;
61 stability_tester.get_liq(o.x, o.rhomolar_liq);
62 stability_tester.get_vap(o.y, o.rhomolar_vap);
63 o.z = HEOS.get_mole_fractions();
64 o.T = HEOS.T();
65 o.p = HEOS.p();
66 o.omega = 1.0;
67 CoolProp::SaturationSolvers::PTflash_twophase solver(HEOS, o);
68 solver.solve();
70 HEOS._Q = (o.z[0] - o.x[0]) / (o.y[0] - o.x[0]); // All vapor qualities are the same (these are the residuals in the solver)
71 HEOS._rhomolar = 1 / (HEOS._Q / HEOS.SatV->rhomolar() + (1 - HEOS._Q) / HEOS.SatL->rhomolar());
72 } else {
73 // It's single-phase
74 double rho = HEOS.solver_rho_Tp_global(HEOS.T(), HEOS.p(), 20000);
75 HEOS.update_DmolarT_direct(rho, HEOS.T());
76 HEOS._Q = -1;
77 HEOS._phase = iphase_liquid;
78 }
79 } else {
80 // It's single-phase, and phase is imposed
81 double rho = HEOS.solver_rho_Tp(HEOS.T(), HEOS.p());
82 HEOS.update_DmolarT_direct(rho, HEOS.T());
83 HEOS._Q = -1;
84 HEOS._phase = HEOS.imposed_phase_index;
85 }
86 }
87}
89 if (HEOS.is_pure_or_pseudopure) {
90 if (HEOS.imposed_phase_index == iphase_not_imposed) // If no phase index is imposed (see set_components function)
91 {
92 // At very low temperature (near the triple point temp), the isotherms are VERY steep
93 // Thus it can be very difficult to determine state based on ps = f(T)
94 // So in this case, we do a phase determination based on p, generally it will be useful enough
95 if (HEOS._T < 0.9 * HEOS.Ttriple() + 0.1 * HEOS.calc_Tmax_sat()) {
96 // Find the phase, while updating all internal variables possible using the pressure
97 bool saturation_called = false;
98 HEOS.p_phase_determination_pure_or_pseudopure(iT, HEOS._T, saturation_called);
99 } else {
100 // Find the phase, while updating all internal variables possible using the temperature
102 }
103 // Check if twophase solution
104 if (!HEOS.isHomogeneousPhase()) {
105 throw ValueError("twophase not implemented yet");
106 }
107 } else {
108 // Phase is imposed. Update _phase in case it was reset elsewhere by another call
109 HEOS._phase = HEOS.imposed_phase_index;
110 }
111 // Find density
112 HEOS._rhomolar = HEOS.solver_rho_Tp(HEOS._T, HEOS._p);
113 HEOS._Q = -1;
114 } else {
115 PT_flash_mixtures(HEOS);
116 }
117}
118
119// Define the residual to be driven to zero
121{
122 public:
126 double call(double T) {
128 CoolPropDbl peos = HEOS->p();
129 CoolPropDbl r = (peos - p) / p;
130 return r;
131 };
132 double deriv(double T) {
133 // dp/dT|rho / pspecified
134 return HEOS->first_partial_deriv(iP, iT, iDmolar) / p;
135 };
136 double second_deriv(double T) {
137 // d2p/dT2|rho / pspecified
139 };
140};
141
142/***
143\f[
144\begin{array}{l}
145p = \frac{{RT}}{{v - b}} - \frac{{a\alpha }}{{v\left( {v + b} \right)}}\\
146\alpha = \left( {1 + \kappa \left( {1 - \sqrt {{T_r}} } \right)} \right)\left( {1 + \kappa \left( {1 - \sqrt {{T_r}} } \right)} \right) = 1 + 2\kappa \left( {1 - \sqrt {{T_r}} } \right) + {\kappa ^2}{\left( {1 - \sqrt {{T_r}} } \right)^2}\\
147\alpha = 1 + 2\kappa \left( {1 - \sqrt {{T_r}} } \right) + {\kappa ^2}{\left( {1 - \sqrt {{T_r}} } \right)^2}\\
148\alpha = 1 + 2\kappa - 2\kappa \sqrt {{T_r}} + {\kappa ^2}\left[ {1 - 2\sqrt {{T_r}} + {T_r}} \right]\\
149T = {T_r}{T_c}\\
150p = \frac{{R{T_r}{T_c}}}{{v - b}} - \frac{{a\left( {1 + 2\kappa - 2\kappa \sqrt {{T_r}} + {\kappa ^2}\left[ {1 - 2\sqrt {{T_r}} + {T_r}} \right]} \right)}}{{v\left( {v + b} \right)}}\\
151\\
152{\rm{Factor in terms of }}\sqrt {{T_r}} \\
153\\
154p = \frac{{R{T_r}{T_c}}}{{v - b}} - \frac{{a\left( {1 + 2\kappa + {\kappa ^2} - 2\kappa \sqrt {{T_r}} + {\kappa ^2}\left[ { - 2\sqrt {{T_r}} + {T_r}} \right]} \right)}}{{v\left( {v + b} \right)}}\\
155p = \frac{{R{T_r}{T_c}}}{{v - b}} - \frac{{a\left( {1 + 2\kappa + {\kappa ^2} - 2\kappa (1 + \kappa )\sqrt {{T_r}} + {\kappa ^2}{T_r}} \right)}}{{v\left( {v + b} \right)}}\\
156p = \frac{{R{T_r}{T_c}}}{{v - b}} - \frac{{a\left( {1 + 2\kappa + {\kappa ^2}} \right)}}{{v\left( {v + b} \right)}} + \frac{{2a\kappa (1 + \kappa )}}{{v\left( {v + b} \right)}}\sqrt {{T_r}} - \frac{{a{\kappa ^2}}}{{v\left( {v + b} \right)}}{T_r}\\
1570 = \left[ {\frac{{R{T_c}}}{{v - b}} - \frac{{a{\kappa ^2}}}{{v\left( {v + b} \right)}}} \right]{T_r} + \frac{{2a\kappa (1 + \kappa )}}{{v\left( {v + b} \right)}}\sqrt {{T_r}} - \frac{{a\left( {1 + 2\kappa + {\kappa ^2}} \right)}}{{v\left( {v + b} \right)}} - p
158\end{array}
159\f]
160 */
161double FlashRoutines::T_DP_PengRobinson(HelmholtzEOSMixtureBackend& HEOS, double rhomolar, double p) {
162 double omega, R, kappa, a, b, A, B, C, Tc, pc, V = 1 / rhomolar;
163 omega = HEOS.acentric_factor();
164 Tc = HEOS.T_critical();
165 pc = HEOS.p_critical();
166 R = HEOS.gas_constant();
167
168 kappa = 0.37464 + 1.54226 * omega - 0.26992 * omega * omega;
169 a = 0.457235 * R * R * Tc * Tc / pc;
170 b = 0.077796 * R * Tc / pc;
171 double den = V * V + 2 * b * V - b * b;
172
173 // A sqrt(Tr)^2 + B sqrt(Tr) + C = 0
174 A = R * Tc / (V - b) - a * kappa * kappa / (den);
175 B = +2 * a * kappa * (1 + kappa) / (den);
176 C = -a * (1 + 2 * kappa + kappa * kappa) / (den)-p;
177
178 //D = B*B-4*A*C;
179
180 double sqrt_Tr1 = (-B + sqrt(B * B - 4 * A * C)) / (2 * A);
181 //double sqrt_Tr2 = (-B-sqrt(B*B-4*A*C))/(2*A);
182 return sqrt_Tr1 * sqrt_Tr1 * Tc;
183};
184
186 // Comment out the check for an imposed phase. There's no code to handle if it is!
187 // Solver below and flash calculations (if two phase) have to be called anyway.
188 //
189 // if (HEOS.imposed_phase_index == iphase_not_imposed) // If no phase index is imposed (see set_components function)
190 // {
191 if (HEOS.is_pure_or_pseudopure) {
192 // Find the phase, while updating all internal variables possible using the pressure
193 bool saturation_called = false;
194 HEOS.p_phase_determination_pure_or_pseudopure(iDmolar, HEOS._rhomolar, saturation_called);
195
196 if (HEOS.isHomogeneousPhase()) {
197 CoolPropDbl T0;
198 if (HEOS._phase == iphase_liquid) {
199 // If it is a liquid, start off at the ancillary value
200 if (saturation_called) {
201 T0 = HEOS.SatL->T();
202 } else {
203 T0 = HEOS._TLanc.pt();
204 }
205 } else if (HEOS._phase == iphase_supercritical_liquid) {
206 // If it is a supercritical
207 T0 = 1.1 * HEOS.T_critical();
208 } else if (HEOS._phase == iphase_gas || HEOS._phase == iphase_supercritical_gas || HEOS._phase == iphase_supercritical) {
209 // First, get a guess for density from Peng-Robinson
210 T0 = T_DP_PengRobinson(HEOS, HEOS.rhomolar(), HEOS.p());
211 } else {
212 throw ValueError("I should never get here");
213 }
214 // Then, do the solver using the full EOS
215 solver_DP_resid resid(&HEOS, HEOS.rhomolar(), HEOS.p());
216 std::string errstr;
217 Halley(resid, T0, 1e-10, 100);
218 HEOS._Q = -1;
219 // Update the state for conditions where the state was guessed
221 if (!get_config_bool(DONT_CHECK_PROPERTY_LIMITS) && HEOS._T > 1.5*HEOS.Tmax()){
222 throw CoolProp::OutOfRangeError(format("DP yielded T > 1.5Tmax w/ T (%g) K").c_str());
223 }
224 } else {
225 // Nothing to do here; phase determination has handled this already
226 }
227 } else {
228 throw NotImplementedError("DP_flash not ready for mixtures");
229 }
230 // }
231 // TO DO: Put the imposed phase check back in
232 // and provide the else code here if it is imposed.
233}
234
236{
237 public:
241 double call(double T) {
242 HEOS.update(QT_INPUTS, 0, T); // Doesn't matter whether liquid or vapor, we are just doing a full VLE call for given T
246 return (1 / rhomolar - 1 / rhoL) / (1 / rhoV - 1 / rhoL) - Q_target;
247 }
248 double deriv(double T) {
249 return _HUGE;
250 }
251 double second_deriv(double T) {
252 return _HUGE;
253 }
254};
255
258 options.use_logdelta = false;
260 if (HEOS.is_pure_or_pseudopure) {
261 // Bump the temperatures to hopefully yield more reliable results
262 double Tmax = HEOS.T_critical() - 0.1;
263 double Tmin = HEOS.Tmin() + 0.1;
264 double rhomolar = HEOS._rhomolar;
265 double Q = HEOS._Q;
266 const double eps = 1e-12; // small tolerance to allow for slop in iterative calculations
267 if (rhomolar >= (HEOS.rhomolar_critical() + eps) && Q > (0 + eps)){
268 throw CoolProp::OutOfRangeError(format("DQ inputs are not defined for density (%g) above critical density (%g) and Q>0", rhomolar, HEOS.rhomolar_critical()).c_str());
269 }
270 DQ_flash_residual resid(HEOS, rhomolar, Q);
271 Brent(resid, Tmin, Tmax, DBL_EPSILON, 1e-10, 100);
272 HEOS._p = HEOS.SatV->p();
273 HEOS._T = HEOS.SatV->T();
274 HEOS._rhomolar = rhomolar;
275 HEOS._Q = Q;
276 HEOS._phase = iphase_twophase;
277 } else {
278 throw NotImplementedError("DQ_flash not ready for mixtures");
279 }
280}
283 options.use_logdelta = false;
285 if (Tguess < 0) {
286 options.use_guesses = true;
287 options.T = Tguess;
288 CoolProp::SaturationAncillaryFunction& rhoL = HEOS.get_components()[0].ancillaries.rhoL;
289 CoolProp::SaturationAncillaryFunction& rhoV = HEOS.get_components()[0].ancillaries.rhoV;
290 options.rhoL = rhoL.evaluate(Tguess);
291 options.rhoV = rhoV.evaluate(Tguess);
292 }
293 if (HEOS.is_pure_or_pseudopure) {
294 if (std::abs(HEOS.Q() - 1) > 1e-10) {
295 throw ValueError(format("non-unity quality not currently allowed for HQ_flash"));
296 }
297 // Do a saturation call for given h for vapor, first with ancillaries, then with full saturation call
299 SaturationSolvers::saturation_PHSU_pure(HEOS, HEOS.hmolar(), options);
300 HEOS._p = HEOS.SatV->p();
301 HEOS._T = HEOS.SatV->T();
302 HEOS._rhomolar = HEOS.SatV->rhomolar();
303 HEOS._phase = iphase_twophase;
304 } else {
305 throw NotImplementedError("HQ_flash not ready for mixtures");
306 }
307}
309 if (HEOS.is_pure_or_pseudopure) {
310
311 if (std::abs(HEOS.smolar() - HEOS.get_state("reducing").smolar) < 0.001) {
312 HEOS._p = HEOS.p_critical();
313 HEOS._T = HEOS.T_critical();
314 HEOS._rhomolar = HEOS.rhomolar_critical();
316 } else if (std::abs(HEOS.Q()) < 1e-10) {
317 // Do a saturation call for given s for liquid, first with ancillaries, then with full saturation call
320 options.use_logdelta = false;
322 SaturationSolvers::saturation_PHSU_pure(HEOS, HEOS.smolar(), options);
323 HEOS._p = HEOS.SatL->p();
324 HEOS._T = HEOS.SatL->T();
325 HEOS._rhomolar = HEOS.SatL->rhomolar();
326 HEOS._phase = iphase_twophase;
327 } else if (std::abs(HEOS.Q() - 1) < 1e-10) {
328 // Do a saturation call for given s for vapor, first with ancillaries, then with full saturation call
331 options.use_logdelta = false;
333 SaturationSolvers::saturation_PHSU_pure(HEOS, HEOS.smolar(), options);
334 HEOS._p = HEOS.SatV->p();
335 HEOS._T = HEOS.SatV->T();
336 HEOS._rhomolar = HEOS.SatV->rhomolar();
337 HEOS._phase = iphase_twophase;
338 } else {
339 throw ValueError(format("non-zero or 1 quality not currently allowed for QS_flash"));
340 }
341 } else {
342 throw NotImplementedError("QS_flash not ready for mixtures");
343 }
344}
346 CoolPropDbl T = HEOS._T;
347 CoolPropDbl Q = HEOS._Q;
348 if (HEOS.is_pure_or_pseudopure) {
349
350 if (get_config_bool(ENABLE_SUPERANCILLARIES) && HEOS.is_pure()){
351 auto& optsuperanc = HEOS.get_superanc_optional();
352 if (optsuperanc){
353 auto& superanc = optsuperanc.value();
354
355 CoolPropDbl Tcrit_num = superanc.get_Tcrit_num();
356 if (T > Tcrit_num){
357 throw ValueError(format("Temperature to QT_flash [%0.8Lg K] may not be above the numerical critical point of %0.15Lg K", T, Tcrit_num));
358 }
359 auto rhoL = superanc.eval_sat(T, 'D', 0);
360 auto rhoV = superanc.eval_sat(T, 'D', 1);
361 auto p = superanc.eval_sat(T, 'P', 1);
362 HEOS.SatL->update_TDmolarP_unchecked(T, rhoL, p);
363 HEOS.SatV->update_TDmolarP_unchecked(T, rhoV, p);
364 HEOS._p = p;
365 HEOS._rhomolar = 1 / (Q / rhoV + (1 - Q) / rhoL);
366 HEOS._phase = iphase_twophase;
367 return;
368 }
369 }
370
371
372 // The maximum possible saturation temperature
373 // Critical point for pure fluids, slightly different for pseudo-pure, very different for mixtures
374 CoolPropDbl Tmax_sat = HEOS.calc_Tmax_sat() + 1e-13;
375
376 // Check what the minimum limits for the equation of state are
377 CoolPropDbl Tmin_satL, Tmin_satV, Tmin_sat;
378 HEOS.calc_Tmin_sat(Tmin_satL, Tmin_satV);
379 Tmin_sat = std::max(Tmin_satL, Tmin_satV) - 1e-13;
380
381 // Get a reference to keep the code a bit cleaner
382 const CriticalRegionSplines& splines = HEOS.components[0].EOS().critical_region_splines;
383
384 if ((get_config_bool(CRITICAL_WITHIN_1UK) && std::abs(T - Tmax_sat) < 1e-6) || std::abs(T - Tmax_sat) < 1e-12) {
385 // If exactly(ish) at the critical temperature, liquid and vapor have the critial density
386 HEOS.SatL->update(DmolarT_INPUTS, HEOS.rhomolar_critical(), HEOS._T);
387 HEOS.SatV->update(DmolarT_INPUTS, HEOS.rhomolar_critical(), HEOS._T);
388 HEOS._rhomolar = HEOS.rhomolar_critical();
389 HEOS._p = 0.5 * HEOS.SatV->p() + 0.5 * HEOS.SatL->p();
390 } else if (!is_in_closed_range(Tmin_sat - 0.1, Tmax_sat, T) && (CoolProp::get_config_bool(DONT_CHECK_PROPERTY_LIMITS) == false)) {
391 throw ValueError(format("Temperature to QT_flash [%0.8Lg K] must be in range [%0.8Lg K, %0.8Lg K]", T, Tmin_sat - 0.1, Tmax_sat));
392 } else if (get_config_bool(CRITICAL_SPLINES_ENABLED) && splines.enabled && HEOS._T > splines.T_min) {
393 double rhoL = _HUGE, rhoV = _HUGE;
394 // Use critical region spline if it has it and temperature is in its range
395 splines.get_densities(T, splines.rhomolar_min, HEOS.rhomolar_critical(), splines.rhomolar_max, rhoL, rhoV);
396 HEOS.SatL->update(DmolarT_INPUTS, rhoL, HEOS._T);
397 HEOS.SatV->update(DmolarT_INPUTS, rhoV, HEOS._T);
398 HEOS._p = 0.5 * HEOS.SatV->p() + 0.5 * HEOS.SatL->p();
399 HEOS._rhomolar = 1 / (HEOS._Q / HEOS.SatV->rhomolar() + (1 - HEOS._Q) / HEOS.SatL->rhomolar());
400 } else if (!(HEOS.components[0].EOS().pseudo_pure)) {
401 // Set some input options
403
404 // Actually call the solver
406
407 HEOS._p = 0.5 * HEOS.SatV->p() + 0.5 * HEOS.SatL->p();
408 HEOS._rhomolar = 1 / (HEOS._Q / HEOS.SatV->rhomolar() + (1 - HEOS._Q) / HEOS.SatL->rhomolar());
409 } else {
410 // Pseudo-pure fluid
411 CoolPropDbl rhoLanc = _HUGE, rhoVanc = _HUGE, rhoLsat = _HUGE, rhoVsat = _HUGE;
412 if (std::abs(HEOS._Q) < DBL_EPSILON) {
413 HEOS._p = HEOS.components[0].ancillaries.pL.evaluate(HEOS._T); // These ancillaries are used explicitly
414 rhoLanc = HEOS.components[0].ancillaries.rhoL.evaluate(HEOS._T);
415 HEOS.SatL->update_TP_guessrho(HEOS._T, HEOS._p, rhoLanc);
416 HEOS._rhomolar = HEOS.SatL->rhomolar();
417 } else if (std::abs(HEOS._Q - 1) < DBL_EPSILON) {
418 HEOS._p = HEOS.components[0].ancillaries.pV.evaluate(HEOS._T); // These ancillaries are used explicitly
419 rhoVanc = HEOS.components[0].ancillaries.rhoV.evaluate(HEOS._T);
420 HEOS.SatV->update_TP_guessrho(HEOS._T, HEOS._p, rhoVanc);
421 HEOS._rhomolar = HEOS.SatV->rhomolar();
422 } else {
423 throw CoolProp::ValueError(format("For pseudo-pure fluid, quality must be equal to 0 or 1. Two-phase quality is not defined"));
424 }
425
426 try {
427 } catch (...) {
428 // Near the critical point, the behavior is not very nice, so we will just use the ancillary
429 rhoLsat = rhoLanc;
430 rhoVsat = rhoVanc;
431 }
432 }
433 // Load the outputs
434 HEOS._phase = iphase_twophase;
435 } else {
436 if (HEOS.PhaseEnvelope.built) {
437 PT_Q_flash_mixtures(HEOS, iT, HEOS._T);
438 } else {
439 // Set some input options
442 options.Nstep_max = 20;
443
444 // Get an extremely rough guess by interpolation of ln(p) v. T curve where the limits are mole-fraction-weighted
446
447 // Use Wilson iteration to obtain updated guess for pressure
448 pguess = SaturationSolvers::saturation_Wilson(HEOS, HEOS._Q, HEOS._T, SaturationSolvers::imposed_T, HEOS.mole_fractions, pguess);
449
450 // Actually call the successive substitution solver
451 SaturationSolvers::successive_substitution(HEOS, HEOS._Q, HEOS._T, pguess, HEOS.mole_fractions, HEOS.K, options);
452
453 // -----
454 // Newton-Raphson
455 // -----
456
457 SaturationSolvers::newton_raphson_saturation NR;
458 SaturationSolvers::newton_raphson_saturation_options IO;
459
460 IO.bubble_point = (HEOS._Q < 0.5);
461
462 IO.x = options.x;
463 IO.y = options.y;
464 IO.rhomolar_liq = options.rhomolar_liq;
465 IO.rhomolar_vap = options.rhomolar_vap;
466 IO.T = options.T;
467 IO.p = options.p;
468 IO.Nstep_max = 30;
469
470 IO.imposed_variable = SaturationSolvers::newton_raphson_saturation_options::T_IMPOSED;
471
472 if (IO.bubble_point) {
473 // Compositions are z, z_incipient
474 NR.call(HEOS, IO.x, IO.y, IO);
475 } else {
476 // Compositions are z, z_incipient
477 NR.call(HEOS, IO.y, IO.x, IO);
478 }
479
480 HEOS._p = IO.p;
481 HEOS._rhomolar = 1 / (HEOS._Q / IO.rhomolar_vap + (1 - HEOS._Q) / IO.rhomolar_liq);
482 }
483 // Load the outputs
484 HEOS._phase = iphase_twophase;
485 HEOS._p = HEOS.SatV->p();
486 HEOS._rhomolar = 1 / (HEOS._Q / HEOS.SatV->rhomolar() + (1 - HEOS._Q) / HEOS.SatL->rhomolar());
487 HEOS._T = HEOS.SatL->T();
488 }
489}
490
491void get_Henrys_coeffs_FP(const std::string& CAS, double& A, double& B, double& C, double& Tmin, double& Tmax) {
492 // Coeffs from Fernandez-Prini JPCRD 2003 DOI: 10.1063/1.1564818
493 if (CAS == "7440-59-7") //Helium
494 {
495 A = -3.52839;
496 B = 7.12983;
497 C = 4.47770;
498 Tmin = 273.21;
499 Tmax = 553.18;
500 } else if (CAS == "7440-01-9") // Ne
501 {
502 A = -3.18301;
503 B = 5.31448;
504 C = 5.43774;
505 Tmin = 273.20;
506 Tmax = 543.36;
507 } else if (CAS == "7440-37-1") // Ar
508 {
509 A = -8.40954;
510 B = 4.29587;
511 C = 10.52779;
512 Tmin = 273.19;
513 Tmax = 568.36;
514 } else if (CAS == "7439-90-9") // Kr
515 {
516 A = -8.97358;
517 B = 3.61508;
518 C = 11.29963;
519 Tmin = 273.19;
520 Tmax = 525.56;
521 } else if (CAS == "7440-63-3") // Xe
522 {
523 A = -14.21635;
524 B = 4.00041;
525 C = 15.60999;
526 Tmin = 273.22;
527 Tmax = 574.85;
528 } else if (CAS == "1333-74-0") // H2
529 {
530 A = -4.73284;
531 B = 6.08954;
532 C = 6.06066;
533 Tmin = 273.15;
534 Tmax = 636.09;
535 } else if (CAS == "7727-37-9") // N2
536 {
537 A = -9.67578;
538 B = 4.72162;
539 C = 11.70585;
540 Tmin = 278.12;
541 Tmax = 636.46;
542 } else if (CAS == "7782-44-7") // O2
543 {
544 A = -9.44833;
545 B = 4.43822;
546 C = 11.42005;
547 Tmin = 274.15;
548 Tmax = 616.52;
549 } else if (CAS == "630-08-0") // CO
550 {
551 A = -10.52862;
552 B = 5.13259;
553 C = 12.01421;
554 Tmin = 278.15;
555 Tmax = 588.67;
556 } else if (CAS == "124-38-9") // CO2
557 {
558 A = -8.55445;
559 B = 4.01195;
560 C = 9.52345;
561 Tmin = 274.19;
562 Tmax = 642.66;
563 } else if (CAS == "7783-06-4") // H2S
564 {
565 A = -4.51499;
566 B = 5.23538;
567 C = 4.42126;
568 Tmin = 273.15;
569 Tmax = 533.09;
570 } else if (CAS == "74-82-8") // CH4
571 {
572 A = -10.44708;
573 B = 4.66491;
574 C = 12.12986;
575 Tmin = 275.46;
576 Tmax = 633.11;
577 } else if (CAS == "74-84-0") // C2H6
578 {
579 A = -19.67563;
580 B = 4.51222;
581 C = 20.62567;
582 Tmin = 275.44;
583 Tmax = 473.46;
584 } else if (CAS == "2551-62-4") // SF6
585 {
586 A = -16.56118;
587 B = 2.15289;
588 C = 20.35440;
589 Tmin = 283.14;
590 Tmax = 505.55;
591 } else {
592 throw ValueError("Bad component in Henry's law constants");
593 }
594}
596 if (HEOS.is_pure_or_pseudopure) {
597
598 if (get_config_bool(ENABLE_SUPERANCILLARIES) && HEOS.is_pure()){
599 auto& optsuperanc = HEOS.get_superanc_optional();
600 if (optsuperanc){
601 auto& superanc = optsuperanc.value();
602 CoolPropDbl pmax_num = superanc.get_pmax();
603 if (HEOS._p > pmax_num){
604 throw ValueError(format("Pressure to PQ_flash [%0.8Lg Pa] may not be above the numerical critical point of %0.15Lg Pa", HEOS._p, pmax_num));
605 }
606 auto T = superanc.get_T_from_p(HEOS._p);
607 auto rhoL = superanc.eval_sat(T, 'D', 0);
608 auto rhoV = superanc.eval_sat(T, 'D', 1);
609 auto p = HEOS._p;
610 HEOS.SatL->update_TDmolarP_unchecked(T, rhoL, p);
611 HEOS.SatV->update_TDmolarP_unchecked(T, rhoV, p);
612 HEOS._T = T;
613 HEOS._p = p;
614 HEOS._rhomolar = 1 / (HEOS._Q / HEOS.SatV->rhomolar() + (1 - HEOS._Q) / HEOS.SatL->rhomolar());
615 HEOS._phase = iphase_twophase;
616 return;
617 }
618 }
619
620 if (HEOS.components[0].EOS().pseudo_pure) {
621 // It is a pseudo-pure mixture
622
623 HEOS._TLanc = HEOS.components[0].ancillaries.pL.invert(HEOS._p);
624 HEOS._TVanc = HEOS.components[0].ancillaries.pV.invert(HEOS._p);
625 // Get guesses for the ancillaries for density
626 CoolPropDbl rhoL = HEOS.components[0].ancillaries.rhoL.evaluate(HEOS._TLanc);
627 CoolPropDbl rhoV = HEOS.components[0].ancillaries.rhoV.evaluate(HEOS._TVanc);
628 // Solve for the density
629 HEOS.SatL->update_TP_guessrho(HEOS._TLanc, HEOS._p, rhoL);
630 HEOS.SatV->update_TP_guessrho(HEOS._TVanc, HEOS._p, rhoV);
631
632 // Load the outputs
633 HEOS._phase = iphase_twophase;
634 HEOS._p = HEOS._Q * HEOS.SatV->p() + (1 - HEOS._Q) * HEOS.SatL->p();
635 HEOS._T = HEOS._Q * HEOS.SatV->T() + (1 - HEOS._Q) * HEOS.SatL->T();
636 HEOS._rhomolar = 1 / (HEOS._Q / HEOS.SatV->rhomolar() + (1 - HEOS._Q) / HEOS.SatL->rhomolar());
637 } else {
638 // Critical point for pure fluids, slightly different for pseudo-pure, very different for mixtures
639 CoolPropDbl pmax_sat = HEOS.calc_pmax_sat();
640
641 // Check what the minimum limits for the equation of state are
642 CoolPropDbl pmin_satL, pmin_satV, pmin_sat;
643 HEOS.calc_pmin_sat(pmin_satL, pmin_satV);
644 pmin_sat = std::max(pmin_satL, pmin_satV);
645
646 // Check for being AT the critical point
647 if (is_in_closed_range(pmax_sat * (1 - 1e-10), pmax_sat * (1 + 1e-10), static_cast<CoolPropDbl>(HEOS._p))) {
648 // Load the outputs
650 HEOS._p = HEOS.p_critical();
651 HEOS._rhomolar = HEOS.rhomolar_critical();
652 HEOS._T = HEOS.T_critical();
653 return;
654 }
655
656 // Check limits
657 if (CoolProp::get_config_bool(DONT_CHECK_PROPERTY_LIMITS) == false) {
658 if (!is_in_closed_range(pmin_sat * 0.999999, pmax_sat * 1.000001, static_cast<CoolPropDbl>(HEOS._p))) {
659 throw ValueError(format("Pressure to PQ_flash [%6g Pa] must be in range [%8Lg Pa, %8Lg Pa]", HEOS._p, pmin_sat, pmax_sat));
660 }
661 }
662 // ------------------
663 // It is a pure fluid
664 // ------------------
665
666 // Set some input options
668 // Specified variable is pressure
670 // Use logarithm of delta as independent variables
671 options.use_logdelta = false;
672
673 double increment = 0.4;
674
675 try {
676 for (double omega = 1.0; omega > 0; omega -= increment) {
677 try {
678 options.omega = omega;
679
680 // Actually call the solver
681 SaturationSolvers::saturation_PHSU_pure(HEOS, HEOS._p, options);
682
683 // If you get here, there was no error, all is well
684 break;
685 } catch (...) {
686 if (omega < 1.1 * increment) {
687 throw;
688 }
689 // else we are going to try again with a smaller omega
690 }
691 }
692 } catch (...) {
693 // We may need to polish the solution at low pressure
695 }
696
697 // Load the outputs
698 HEOS._phase = iphase_twophase;
699 HEOS._p = HEOS._Q * HEOS.SatV->p() + (1 - HEOS._Q) * HEOS.SatL->p();
700 HEOS._rhomolar = 1 / (HEOS._Q / HEOS.SatV->rhomolar() + (1 - HEOS._Q) / HEOS.SatL->rhomolar());
701 HEOS._T = HEOS.SatL->T();
702 }
703 } else {
704 if (HEOS.PhaseEnvelope.built) {
705 PT_Q_flash_mixtures(HEOS, iP, HEOS._p);
706 } else {
707
708 // Set some input options
711 io.Nstep_max = 10;
712
713 // Get an extremely rough guess by interpolation of ln(p) v. T curve where the limits are mole-fraction-weighted
715
716 // Use Wilson iteration to obtain updated guess for temperature
717 Tguess = SaturationSolvers::saturation_Wilson(HEOS, HEOS._Q, HEOS._p, SaturationSolvers::imposed_p, HEOS.mole_fractions, Tguess);
718
719 std::vector<CoolPropDbl> K = HEOS.K;
720
721 if (get_config_bool(HENRYS_LAW_TO_GENERATE_VLE_GUESSES) && std::abs(HEOS._Q - 1) < 1e-10) {
722 const std::vector<CoolPropFluid>& components = HEOS.get_components();
723 std::size_t iWater = 0;
724 double p1star = PropsSI("P", "T", Tguess, "Q", 1, "Water");
725 const std::vector<CoolPropDbl> y = HEOS.mole_fractions;
726 std::vector<CoolPropDbl> x(y.size());
727 for (std::size_t i = 0; i < components.size(); ++i) {
728 if (components[i].CAS == "7732-18-5") {
729 iWater = i;
730 continue;
731 } else {
732 double A, B, C, Tmin, Tmax;
733 get_Henrys_coeffs_FP(components[i].CAS, A, B, C, Tmin, Tmax);
734 double T_R = Tguess / 647.096, tau = 1 - T_R;
735 double k_H = p1star * exp(A / T_R + B * pow(tau, 0.355) / T_R + C * pow(T_R, -0.41) * exp(tau));
736 x[i] = y[i] * HEOS._p / k_H;
737 //
738 K[i] = y[i] / x[i];
739 }
740 }
741 // Update water K factor
742 double summer = 0;
743 for (std::size_t i = 0; i < y.size(); ++i) {
744 if (i != iWater) {
745 summer += x[i];
746 }
747 }
748 x[iWater] = summer;
749 K[iWater] = y[iWater] / x[iWater];
750 }
751
752 // Actually call the successive substitution solver
753 SaturationSolvers::successive_substitution(HEOS, HEOS._Q, Tguess, HEOS._p, HEOS.mole_fractions, K, io);
754
755 // -----
756 // Newton-Raphson
757 // -----
758
759 SaturationSolvers::newton_raphson_saturation NR;
760 SaturationSolvers::newton_raphson_saturation_options IO;
761
762 IO.bubble_point = (HEOS._Q < 0.5);
763 IO.x = io.x;
764 IO.y = io.y;
765 IO.rhomolar_liq = io.rhomolar_liq;
766 IO.rhomolar_vap = io.rhomolar_vap;
767 IO.T = io.T;
768 IO.p = io.p;
769 IO.Nstep_max = 30;
770 IO.imposed_variable = SaturationSolvers::newton_raphson_saturation_options::P_IMPOSED;
771
772 if (IO.bubble_point) {
773 // Compositions are z, z_incipient
774 NR.call(HEOS, IO.x, IO.y, IO);
775 } else {
776 // Compositions are z, z_incipient
777 NR.call(HEOS, IO.y, IO.x, IO);
778 }
779 }
780
781 // Load the outputs
782 HEOS._phase = iphase_twophase;
783 HEOS._p = HEOS.SatV->p();
784 HEOS._rhomolar = 1 / (HEOS._Q / HEOS.SatV->rhomolar() + (1 - HEOS._Q) / HEOS.SatL->rhomolar());
785 HEOS._T = HEOS.SatL->T();
786 }
787}
788
790 SaturationSolvers::newton_raphson_saturation NR;
791 SaturationSolvers::newton_raphson_saturation_options IO;
792 IO.rhomolar_liq = guess.rhomolar_liq;
793 IO.rhomolar_vap = guess.rhomolar_vap;
794 IO.x = std::vector<CoolPropDbl>(guess.x.begin(), guess.x.end());
795 IO.y = std::vector<CoolPropDbl>(guess.y.begin(), guess.y.end());
796 IO.T = guess.T;
797 IO.p = HEOS._p;
798 IO.bubble_point = false;
799 IO.imposed_variable = SaturationSolvers::newton_raphson_saturation_options::P_IMPOSED;
800
801 if (std::abs(HEOS.Q()) < 1e-10) {
802 IO.bubble_point = true;
803 NR.call(HEOS, IO.x, IO.y, IO);
804 } else if (std::abs(HEOS.Q() - 1) < 1e-10) {
805 IO.bubble_point = false;
806 NR.call(HEOS, IO.y, IO.x, IO);
807 } else {
808 throw ValueError(format("Quality must be 0 or 1"));
809 }
810
811 // Load the other outputs
812 HEOS._phase = iphase_twophase;
813 HEOS._rhomolar = 1 / (HEOS._Q / IO.rhomolar_vap + (1 - HEOS._Q) / IO.rhomolar_liq);
814 HEOS._T = IO.T;
815}
817 SaturationSolvers::newton_raphson_saturation NR;
818 SaturationSolvers::newton_raphson_saturation_options IO;
819 IO.rhomolar_liq = guess.rhomolar_liq;
820 IO.rhomolar_vap = guess.rhomolar_vap;
821 IO.x = std::vector<CoolPropDbl>(guess.x.begin(), guess.x.end());
822 IO.y = std::vector<CoolPropDbl>(guess.y.begin(), guess.y.end());
823 IO.T = HEOS._T;
824 IO.p = guess.p;
825 IO.bubble_point = false;
826 IO.imposed_variable = SaturationSolvers::newton_raphson_saturation_options::T_IMPOSED;
827
828 if (get_debug_level() > 9) {
829 std::cout << format(" QT w/ guess p %g T %g dl %g dv %g x %s y %s\n", IO.p, IO.T, IO.rhomolar_liq, IO.rhomolar_vap,
830 vec_to_string(IO.x, "%g").c_str(), vec_to_string(IO.y, "%g").c_str());
831 }
832
833 if (std::abs(HEOS.Q()) < 1e-10) {
834 IO.bubble_point = true;
835 NR.call(HEOS, IO.x, IO.y, IO);
836 } else if (std::abs(HEOS.Q() - 1) < 1e-10) {
837 IO.bubble_point = false;
838 NR.call(HEOS, IO.y, IO.x, IO);
839 } else {
840 throw ValueError(format("Quality must be 0 or 1"));
841 }
842
843 // Load the other outputs
844 HEOS._p = IO.p;
845 HEOS._phase = iphase_twophase;
846 HEOS._rhomolar = 1 / (HEOS._Q / IO.rhomolar_vap + (1 - HEOS._Q) / IO.rhomolar_liq);
847}
848
850 HEOS.solver_rho_Tp(HEOS.T(), HEOS.p(), guess.rhomolar);
851 // Load the other outputs
852 HEOS._phase = iphase_gas; // Guessed for mixtures
853 if (HEOS.is_pure_or_pseudopure) {
854 if (HEOS._p > HEOS.p_critical()) {
855 if (HEOS._T > HEOS.T_critical()) {
857 } else {
859 }
860 } else {
861 if (HEOS._T > HEOS.T_critical()) {
863 } else if (HEOS._rhomolar > HEOS.rhomolar_critical()) {
864 HEOS._phase = iphase_liquid;
865 } else {
866 HEOS._phase = iphase_gas;
867 }
868 }
869 }
870
871 HEOS._Q = -1;
872}
873
875
876 // Find the intersections in the phase envelope
877 std::vector<std::pair<std::size_t, std::size_t>> intersections =
879
881
882 enum quality_options
883 {
884 SATURATED_LIQUID,
885 SATURATED_VAPOR,
886 TWO_PHASE
887 };
888 quality_options quality;
889 if (std::abs(HEOS._Q) < 100 * DBL_EPSILON) {
890 quality = SATURATED_LIQUID;
891 } else if (std::abs(HEOS._Q - 1) < 100 * DBL_EPSILON) {
892 quality = SATURATED_VAPOR;
893 } else if (HEOS._Q > 0 && HEOS._Q < 1) {
894 quality = TWO_PHASE;
895 } else {
896 throw ValueError("Quality is not within 0 and 1");
897 }
898
899 if (quality == SATURATED_LIQUID || quality == SATURATED_VAPOR) {
900 // *********************************************************
901 // Bubble- or dew-point calculation
902 // *********************************************************
903 // Find the correct solution
904 std::vector<std::size_t> solutions;
905 for (std::vector<std::pair<std::size_t, std::size_t>>::const_iterator it = intersections.begin(); it != intersections.end(); ++it) {
906 if (std::abs(env.Q[it->first] - HEOS._Q) < 10 * DBL_EPSILON && std::abs(env.Q[it->second] - HEOS._Q) < 10 * DBL_EPSILON) {
907 solutions.push_back(it->first);
908 }
909 }
910
911 if (solutions.size() == 1) {
912
913 std::size_t& imax = solutions[0];
914
915 // Shift the solution if needed to ensure that imax+2 and imax-1 are both in range
916 if (imax + 2 >= env.T.size()) {
917 imax--;
918 } else if (imax == 0) {
919 imax++;
920 }
921 // Here imax+2 or imax-1 is still possibly out of range:
922 // 1. If imax initially is 1, and env.T.size() <= 3, then imax will become 0.
923 // 2. If imax initially is 0, and env.T.size() <= 2, then imax will become MAX_UINT.
924 // 3. If imax+2 initially is more than env.T.size(), then single decrement will not bring it to range
925
926 SaturationSolvers::newton_raphson_saturation NR;
927 SaturationSolvers::newton_raphson_saturation_options IO;
928
929 if (other == iP) {
930 IO.p = HEOS._p;
931 IO.imposed_variable = SaturationSolvers::newton_raphson_saturation_options::P_IMPOSED;
932 // p -> rhomolar_vap
933 IO.rhomolar_vap = CubicInterp(env.p, env.rhomolar_vap, imax - 1, imax, imax + 1, imax + 2, static_cast<CoolPropDbl>(IO.p));
934 IO.T = CubicInterp(env.rhomolar_vap, env.T, imax - 1, imax, imax + 1, imax + 2, IO.rhomolar_vap);
935 } else if (other == iT) {
936 IO.T = HEOS._T;
937 IO.imposed_variable = SaturationSolvers::newton_raphson_saturation_options::T_IMPOSED;
938 // T -> rhomolar_vap
939 IO.rhomolar_vap = CubicInterp(env.T, env.rhomolar_vap, imax - 1, imax, imax + 1, imax + 2, static_cast<CoolPropDbl>(IO.T));
940 IO.p = CubicInterp(env.rhomolar_vap, env.p, imax - 1, imax, imax + 1, imax + 2, IO.rhomolar_vap);
941 } else {
942 throw ValueError();
943 }
944 IO.rhomolar_liq = CubicInterp(env.rhomolar_vap, env.rhomolar_liq, imax - 1, imax, imax + 1, imax + 2, IO.rhomolar_vap);
945
946 if (quality == SATURATED_VAPOR) {
947 IO.bubble_point = false;
948 IO.y = HEOS.get_mole_fractions(); // Because Q = 1
949 IO.x.resize(IO.y.size());
950 for (std::size_t i = 0; i < IO.x.size() - 1; ++i) // First N-1 elements
951 {
952 IO.x[i] = CubicInterp(env.rhomolar_vap, env.x[i], imax - 1, imax, imax + 1, imax + 2, IO.rhomolar_vap);
953 }
954 IO.x[IO.x.size() - 1] = 1 - std::accumulate(IO.x.begin(), IO.x.end() - 1, 0.0);
955 NR.call(HEOS, IO.y, IO.x, IO);
956 } else {
957 IO.bubble_point = true;
958 IO.x = HEOS.get_mole_fractions(); // Because Q = 0
959 IO.y.resize(IO.x.size());
960 // Phases are inverted, so "liquid" is actually the lighter phase
961 std::swap(IO.rhomolar_liq, IO.rhomolar_vap);
962 for (std::size_t i = 0; i < IO.y.size() - 1; ++i) // First N-1 elements
963 {
964 // Phases are inverted, so liquid mole fraction (x) of phase envelope is actually the vapor phase mole fraction
965 // Use the liquid density as well
966 IO.y[i] = CubicInterp(env.rhomolar_vap, env.x[i], imax - 1, imax, imax + 1, imax + 2, IO.rhomolar_liq);
967 }
968 IO.y[IO.y.size() - 1] = 1 - std::accumulate(IO.y.begin(), IO.y.end() - 1, 0.0);
969 NR.call(HEOS, IO.x, IO.y, IO);
970 }
971 } else if (solutions.size() == 0) {
972 throw ValueError("No solution was found in PQ_flash");
973 } else {
974 throw ValueError("More than 1 solution was found in PQ_flash");
975 }
976 } else {
977 // *********************************************************
978 // Two-phase calculation for given vapor quality
979 // *********************************************************
980
981 // Find the correct solution
982 std::vector<std::size_t> liquid_solutions, vapor_solutions;
983 for (std::vector<std::pair<std::size_t, std::size_t>>::const_iterator it = intersections.begin(); it != intersections.end(); ++it) {
984 if (std::abs(env.Q[it->first] - 0) < 10 * DBL_EPSILON && std::abs(env.Q[it->second] - 0) < 10 * DBL_EPSILON) {
985 liquid_solutions.push_back(it->first);
986 }
987 if (std::abs(env.Q[it->first] - 1) < 10 * DBL_EPSILON && std::abs(env.Q[it->second] - 1) < 10 * DBL_EPSILON) {
988 vapor_solutions.push_back(it->first);
989 }
990 }
991
992 if (liquid_solutions.size() != 1 || vapor_solutions.size() != 1) {
993 throw ValueError(format("Number liquid solutions [%d] or vapor solutions [%d] != 1", liquid_solutions.size(), vapor_solutions.size()));
994 }
995 std::size_t iliq = liquid_solutions[0], ivap = vapor_solutions[0];
996
997 SaturationSolvers::newton_raphson_twophase NR;
998 SaturationSolvers::newton_raphson_twophase_options IO;
999 IO.beta = HEOS._Q;
1000
1001 CoolPropDbl rhomolar_vap_sat_vap, T_sat_vap, rhomolar_liq_sat_vap, rhomolar_liq_sat_liq, T_sat_liq, rhomolar_vap_sat_liq, p_sat_liq,
1002 p_sat_vap;
1003
1004 if (other == iP) {
1005 IO.p = HEOS._p;
1006 p_sat_liq = IO.p;
1007 p_sat_vap = IO.p;
1008 IO.imposed_variable = SaturationSolvers::newton_raphson_twophase_options::P_IMPOSED;
1009
1010 // Calculate the interpolated values for beta = 0 and beta = 1
1011 rhomolar_vap_sat_vap = CubicInterp(env.p, env.rhomolar_vap, ivap - 1, ivap, ivap + 1, ivap + 2, static_cast<CoolPropDbl>(IO.p));
1012 T_sat_vap = CubicInterp(env.rhomolar_vap, env.T, ivap - 1, ivap, ivap + 1, ivap + 2, rhomolar_vap_sat_vap);
1013 rhomolar_liq_sat_vap = CubicInterp(env.rhomolar_vap, env.rhomolar_liq, ivap - 1, ivap, ivap + 1, ivap + 2, rhomolar_vap_sat_vap);
1014
1015 // Phase inversion for liquid solution (liquid is vapor and vice versa)
1016 rhomolar_liq_sat_liq = CubicInterp(env.p, env.rhomolar_vap, iliq - 1, iliq, iliq + 1, iliq + 2, static_cast<CoolPropDbl>(IO.p));
1017 T_sat_liq = CubicInterp(env.rhomolar_vap, env.T, iliq - 1, iliq, iliq + 1, iliq + 2, rhomolar_liq_sat_liq);
1018 rhomolar_vap_sat_liq = CubicInterp(env.rhomolar_vap, env.rhomolar_liq, iliq - 1, iliq, iliq + 1, iliq + 2, rhomolar_liq_sat_liq);
1019 } else if (other == iT) {
1020 IO.T = HEOS._T;
1021 T_sat_liq = IO.T;
1022 T_sat_vap = IO.T;
1023 IO.imposed_variable = SaturationSolvers::newton_raphson_twophase_options::T_IMPOSED;
1024
1025 // Calculate the interpolated values for beta = 0 and beta = 1
1026 rhomolar_vap_sat_vap = CubicInterp(env.T, env.rhomolar_vap, ivap - 1, ivap, ivap + 1, ivap + 2, static_cast<CoolPropDbl>(IO.T));
1027 p_sat_vap = CubicInterp(env.rhomolar_vap, env.p, ivap - 1, ivap, ivap + 1, ivap + 2, rhomolar_vap_sat_vap);
1028 rhomolar_liq_sat_vap = CubicInterp(env.rhomolar_vap, env.rhomolar_liq, ivap - 1, ivap, ivap + 1, ivap + 2, rhomolar_vap_sat_vap);
1029
1030 // Phase inversion for liquid solution (liquid is vapor and vice versa)
1031 rhomolar_liq_sat_liq = CubicInterp(env.T, env.rhomolar_vap, iliq - 1, iliq, iliq + 1, iliq + 2, static_cast<CoolPropDbl>(IO.T));
1032 p_sat_liq = CubicInterp(env.rhomolar_vap, env.p, iliq - 1, iliq, iliq + 1, iliq + 2, rhomolar_liq_sat_liq);
1033 rhomolar_vap_sat_liq = CubicInterp(env.rhomolar_vap, env.rhomolar_liq, iliq - 1, iliq, iliq + 1, iliq + 2, rhomolar_liq_sat_liq);
1034 } else {
1035 throw ValueError();
1036 }
1037
1038 // Weight the guesses by the vapor mole fraction
1039 IO.rhomolar_vap = IO.beta * rhomolar_vap_sat_vap + (1 - IO.beta) * rhomolar_vap_sat_liq;
1040 IO.rhomolar_liq = IO.beta * rhomolar_liq_sat_vap + (1 - IO.beta) * rhomolar_liq_sat_liq;
1041 IO.T = IO.beta * T_sat_vap + (1 - IO.beta) * T_sat_liq;
1042 IO.p = IO.beta * p_sat_vap + (1 - IO.beta) * p_sat_liq;
1043
1044 IO.z = HEOS.get_mole_fractions();
1045 IO.x.resize(IO.z.size());
1046 IO.y.resize(IO.z.size());
1047
1048 for (std::size_t i = 0; i < IO.x.size() - 1; ++i) // First N-1 elements
1049 {
1050 CoolPropDbl x_sat_vap = CubicInterp(env.rhomolar_vap, env.x[i], ivap - 1, ivap, ivap + 1, ivap + 2, rhomolar_vap_sat_vap);
1051 CoolPropDbl y_sat_vap = CubicInterp(env.rhomolar_vap, env.y[i], ivap - 1, ivap, ivap + 1, ivap + 2, rhomolar_vap_sat_vap);
1052
1053 CoolPropDbl x_sat_liq = CubicInterp(env.rhomolar_vap, env.y[i], iliq - 1, iliq, iliq + 1, iliq + 2, rhomolar_liq_sat_liq);
1054 CoolPropDbl y_sat_liq = CubicInterp(env.rhomolar_vap, env.x[i], iliq - 1, iliq, iliq + 1, iliq + 2, rhomolar_liq_sat_liq);
1055
1056 IO.x[i] = IO.beta * x_sat_vap + (1 - IO.beta) * x_sat_liq;
1057 IO.y[i] = IO.beta * y_sat_vap + (1 - IO.beta) * y_sat_liq;
1058 }
1059 IO.x[IO.x.size() - 1] = 1 - std::accumulate(IO.x.begin(), IO.x.end() - 1, 0.0);
1060 IO.y[IO.y.size() - 1] = 1 - std::accumulate(IO.y.begin(), IO.y.end() - 1, 0.0);
1061 NR.call(HEOS, IO);
1062 }
1063}
1065 class Residual : public FuncWrapper1D
1066 {
1067
1068 public:
1070 CoolPropDbl rhomolar_spec; // Specified value for density
1071 parameters other; // Key for other value
1072 CoolPropDbl value; // value for S,H,U
1073 CoolPropDbl Qd; // Quality from density
1074 Residual(HelmholtzEOSMixtureBackend& HEOS, CoolPropDbl rhomolar_spec, parameters other, CoolPropDbl value)
1075 : HEOS(HEOS), rhomolar_spec(rhomolar_spec), other(other), value(value) {
1076 Qd = _HUGE;
1077 };
1078 double call(double T) {
1079 HEOS.update(QT_INPUTS, 0, T);
1080 HelmholtzEOSMixtureBackend &SatL = HEOS.get_SatL(), &SatV = HEOS.get_SatV();
1081 // Quality from density
1082 Qd = (1 / rhomolar_spec - 1 / SatL.rhomolar()) / (1 / SatV.rhomolar() - 1 / SatL.rhomolar());
1083 // Quality from other parameter (H,S,U)
1084 CoolPropDbl Qo = (value - SatL.keyed_output(other)) / (SatV.keyed_output(other) - SatL.keyed_output(other));
1085 // Residual is the difference between the two
1086 return Qo - Qd;
1087 }
1088 } resid(HEOS, rhomolar_spec, other, value);
1089
1090 // Critical point for pure fluids, slightly different for pseudo-pure, very different for mixtures
1091 CoolPropDbl Tmax_sat = HEOS.calc_Tmax_sat() - 1e-13;
1092
1093 // Check what the minimum limits for the equation of state are
1094 CoolPropDbl Tmin_satL, Tmin_satV, Tmin_sat;
1095 HEOS.calc_Tmin_sat(Tmin_satL, Tmin_satV);
1096 Tmin_sat = std::max(Tmin_satL, Tmin_satV) - 1e-13;
1097
1098 Brent(resid, Tmin_sat, Tmax_sat - 0.01, DBL_EPSILON, 1e-12, 20);
1099 // Solve once more with the final vapor quality
1100 HEOS.update(QT_INPUTS, resid.Qd, HEOS.T());
1101}
1102// D given and one of P,H,S,U
1104 // Define the residual to be driven to zero
1105 class solver_resid : public FuncWrapper1DWithTwoDerivs
1106 {
1107 public:
1109 CoolPropDbl rhomolar, value;
1110 parameters other;
1111 CoolPropDbl Tmin, Tmax;
1112
1113 solver_resid(HelmholtzEOSMixtureBackend* HEOS, CoolPropDbl rhomolar, CoolPropDbl value, parameters other, CoolPropDbl Tmin, CoolPropDbl Tmax)
1114 : HEOS(HEOS), rhomolar(rhomolar), value(value), other(other), Tmin(Tmin), Tmax(Tmax) {
1117 };
1118 double call(double T) {
1119 HEOS->update_DmolarT_direct(rhomolar, T);
1120 double eos = HEOS->keyed_output(other);
1121 if (other == iP) {
1122 // For p, should use fractional error
1123 return (eos - value) / value;
1124 } else {
1125 // For everything else, use absolute error
1126 return eos - value;
1127 }
1128 };
1129 double deriv(double T) {
1130 if (other == iP) {
1131 return HEOS->first_partial_deriv(other, iT, iDmolar) / value;
1132 }
1133 return HEOS->first_partial_deriv(other, iT, iDmolar);
1134 };
1135 double second_deriv(double T) {
1136 if (other == iP) {
1137 return HEOS->second_partial_deriv(other, iT, iDmolar, iT, iDmolar) / value;
1138 }
1139 return HEOS->second_partial_deriv(other, iT, iDmolar, iT, iDmolar);
1140 };
1141 bool input_not_in_range(double T) {
1142 return (T < Tmin || T > Tmax);
1143 }
1144 };
1145
1146 if (HEOS.is_pure_or_pseudopure) {
1147 CoolPropFluid& component = HEOS.components[0];
1148
1149 shared_ptr<HelmholtzEOSMixtureBackend> Sat;
1150 CoolPropDbl rhoLtriple = component.triple_liquid.rhomolar;
1151 CoolPropDbl rhoVtriple = component.triple_vapor.rhomolar;
1152 // Check if in the "normal" region
1153 if (HEOS._rhomolar >= rhoVtriple && HEOS._rhomolar <= rhoLtriple) {
1154 CoolPropDbl yL, yV, value, y_solid;
1155 CoolPropDbl TLtriple = component.triple_liquid.T;
1156 CoolPropDbl TVtriple = component.triple_vapor.T;
1157
1158 // First check if solid (below the line connecting the triple point values) - this is an error for now
1159 switch (other) {
1160 case iSmolar:
1161 yL = HEOS.calc_smolar_nocache(TLtriple, rhoLtriple);
1162 yV = HEOS.calc_smolar_nocache(TVtriple, rhoVtriple);
1163 value = HEOS._smolar;
1164 break;
1165 case iHmolar:
1166 yL = HEOS.calc_hmolar_nocache(TLtriple, rhoLtriple);
1167 yV = HEOS.calc_hmolar_nocache(TVtriple, rhoVtriple);
1168 value = HEOS._hmolar;
1169 break;
1170 case iUmolar:
1171 yL = HEOS.calc_umolar_nocache(TLtriple, rhoLtriple);
1172 yV = HEOS.calc_umolar_nocache(TVtriple, rhoVtriple);
1173 value = HEOS._umolar;
1174 break;
1175 case iP:
1176 yL = HEOS.calc_pressure_nocache(TLtriple, rhoLtriple);
1177 yV = HEOS.calc_pressure_nocache(TVtriple, rhoVtriple);
1178 value = HEOS._p;
1179 break;
1180 default:
1181 throw ValueError(format("Input is invalid"));
1182 }
1183 y_solid = (yV - yL) / (1 / rhoVtriple - 1 / rhoLtriple) * (1 / HEOS._rhomolar - 1 / rhoLtriple) + yL;
1184
1185 if (value < y_solid) {
1186 throw ValueError(format("Other input [%d:%g] is solid", other, value));
1187 }
1188
1189 // Check if other is above the saturation value.
1191 optionsD.omega = 1;
1192 optionsD.use_logdelta = false;
1193 optionsD.max_iterations = 200;
1194 for (int i_try = 0; i_try < 7; i_try++)
1195 {
1196 try
1197 {
1198 if (HEOS._rhomolar > HEOS._crit.rhomolar)
1199 {
1201 SaturationSolvers::saturation_D_pure(HEOS, HEOS._rhomolar, optionsD);
1202 // SatL and SatV have the saturation values
1203 Sat = HEOS.SatL;
1204 }
1205 else
1206 {
1208 SaturationSolvers::saturation_D_pure(HEOS, HEOS._rhomolar, optionsD);
1209 // SatL and SatV have the saturation values
1210 Sat = HEOS.SatV;
1211 }
1212 break; // good solve
1213 }
1214 catch(CoolPropBaseError)
1215 {
1216 optionsD.omega /= 2;
1217 optionsD.max_iterations *= 2;
1218 if (i_try >= 6){throw;}
1219 }
1220 }
1221
1222 // If it is above, it is not two-phase and either liquid, vapor or supercritical
1223 if (value > Sat->keyed_output(other)) {
1224 solver_resid resid(&HEOS, HEOS._rhomolar, value, other, Sat->keyed_output(iT), HEOS.Tmax() * 1.5);
1225 try {
1226 HEOS._T = Halley(resid, 0.5 * (Sat->keyed_output(iT) + HEOS.Tmax() * 1.5), 1e-10, 100);
1227 } catch (...) {
1228 HEOS._T = Brent(resid, Sat->keyed_output(iT), HEOS.Tmax() * 1.5, DBL_EPSILON, 1e-12, 100);
1229 }
1230 HEOS._Q = 10000;
1231 HEOS._p = HEOS.calc_pressure_nocache(HEOS.T(), HEOS.rhomolar());
1232 HEOS.unspecify_phase();
1233 // Update the phase flag
1235 } else {
1236 // Now we know that temperature is between Tsat(D) +- tolerance and the minimum temperature for the fluid
1237 if (other == iP) {
1238 // Iterate to find T(p), its just a saturation call
1239
1240 // Set some input options
1242 // Specified variable is pressure
1244 // Use logarithm of delta as independent variables
1245 optionsPHSU.use_logdelta = false;
1246
1247 // Actually call the solver
1248 SaturationSolvers::saturation_PHSU_pure(HEOS, HEOS._p, optionsPHSU);
1249
1250 // Load the outputs
1251 HEOS._phase = iphase_twophase;
1252 HEOS._Q = (1 / HEOS._rhomolar - 1 / HEOS.SatL->rhomolar()) / (1 / HEOS.SatV->rhomolar() - 1 / HEOS.SatL->rhomolar());
1253 HEOS._T = HEOS.SatL->T();
1254 } else {
1255 // Residual is difference in quality calculated from density and quality calculated from the other parameter
1256 // Iterate to find T
1257 HSU_D_flash_twophase(HEOS, HEOS._rhomolar, other, value);
1258 HEOS._phase = iphase_twophase;
1259 }
1260 }
1261 }
1262 // Check if vapor/solid region below triple point vapor density
1263 else if (HEOS._rhomolar < component.triple_vapor.rhomolar) {
1264 CoolPropDbl y, value;
1265 CoolPropDbl TVtriple = component.triple_vapor.T; //TODO: separate TL and TV for ppure
1266
1267 // If value is above the value calculated from X(Ttriple, _rhomolar), it is vapor
1268 switch (other) {
1269 case iSmolar:
1270 y = HEOS.calc_smolar_nocache(TVtriple, HEOS._rhomolar);
1271 value = HEOS._smolar;
1272 break;
1273 case iHmolar:
1274 y = HEOS.calc_hmolar_nocache(TVtriple, HEOS._rhomolar);
1275 value = HEOS._hmolar;
1276 break;
1277 case iUmolar:
1278 y = HEOS.calc_umolar_nocache(TVtriple, HEOS._rhomolar);
1279 value = HEOS._umolar;
1280 break;
1281 case iP:
1282 y = HEOS.calc_pressure_nocache(TVtriple, HEOS._rhomolar);
1283 value = HEOS._p;
1284 break;
1285 default:
1286 throw ValueError(format("Input is invalid"));
1287 }
1288 if (value > y) {
1289 solver_resid resid(&HEOS, HEOS._rhomolar, value, other, TVtriple, HEOS.Tmax() * 1.5);
1290 HEOS._phase = iphase_gas;
1291 try {
1292 HEOS._T = Halley(resid, 0.5 * (TVtriple + HEOS.Tmax() * 1.5), DBL_EPSILON, 100);
1293 } catch (...) {
1294 HEOS._T = Brent(resid, TVtriple, HEOS.Tmax() * 1.5, DBL_EPSILON, 1e-12, 100);
1295 }
1296 HEOS._Q = 10000;
1297 HEOS.calc_pressure();
1298 } else {
1299 throw ValueError(format("D < DLtriple %g %g", value, y));
1300 }
1301
1302 }
1303 // Check in the liquid/solid region above the triple point density
1304 else {
1305 CoolPropDbl y, value;
1306 CoolPropDbl TLtriple = component.EOS().Ttriple;
1307
1308 // If value is above the value calculated from X(Ttriple, _rhomolar), it is vapor
1309 switch (other) {
1310 case iSmolar:
1311 y = HEOS.calc_smolar_nocache(TLtriple, HEOS._rhomolar);
1312 value = HEOS._smolar;
1313 break;
1314 case iHmolar:
1315 y = HEOS.calc_hmolar_nocache(TLtriple, HEOS._rhomolar);
1316 value = HEOS._hmolar;
1317 break;
1318 case iUmolar:
1319 y = HEOS.calc_umolar_nocache(TLtriple, HEOS._rhomolar);
1320 value = HEOS._umolar;
1321 break;
1322 case iP:
1323 y = HEOS.calc_pressure_nocache(TLtriple, HEOS._rhomolar);
1324 value = HEOS._p;
1325 break;
1326 default:
1327 throw ValueError(format("Input is invalid"));
1328 }
1329 if (value > y) {
1330 solver_resid resid(&HEOS, HEOS._rhomolar, value, other, TLtriple, HEOS.Tmax() * 1.5);
1331 HEOS._phase = iphase_liquid;
1332 try {
1333 HEOS._T = Halley(resid, 0.5 * (TLtriple + HEOS.Tmax() * 1.5), DBL_EPSILON, 100);
1334 } catch (...) {
1335 HEOS._T = Brent(resid, TLtriple, HEOS.Tmax() * 1.5, DBL_EPSILON, 1e-12, 100);
1336 }
1337 HEOS._Q = 10000;
1338 HEOS.calc_pressure();
1339 } else {
1340 throw ValueError(format("D < DLtriple %g %g", value, y));
1341 }
1342 }
1343 // Update the state for conditions where the state was guessed
1344 if (HEOS.phase() != iphase_twophase) {
1346 }
1347 } else
1348 throw NotImplementedError("PHSU_D_flash not ready for mixtures");
1349}
1350
1352 double A[2][2], B[2][2];
1353 CoolPropDbl y = _HUGE;
1355 _HEOS.update(DmolarT_INPUTS, rhomolar0, T0);
1356 CoolPropDbl Tc = HEOS.calc_T_critical();
1357 CoolPropDbl rhoc = HEOS.calc_rhomolar_critical();
1358 CoolPropDbl R = HEOS.gas_constant();
1359 CoolPropDbl p = HEOS.p();
1360 switch (other) {
1361 case iHmolar:
1362 y = HEOS.hmolar();
1363 break;
1364 case iSmolar:
1365 y = HEOS.smolar();
1366 break;
1367 default:
1368 throw ValueError("other is invalid in HSU_P_flash_singlephase_Newton");
1369 }
1370
1371 CoolPropDbl worst_error = 999;
1372 int iter = 0;
1373 bool failed = false;
1374 CoolPropDbl omega = 1.0, f2, df2_dtau, df2_ddelta;
1375 CoolPropDbl tau = _HEOS.tau(), delta = _HEOS.delta();
1376 while (worst_error > 1e-6 && failed == false) {
1377
1378 // All the required partial derivatives
1379 CoolPropDbl a0 = _HEOS.calc_alpha0_deriv_nocache(0, 0, HEOS.mole_fractions, tau, delta, Tc, rhoc);
1380 CoolPropDbl da0_ddelta = _HEOS.calc_alpha0_deriv_nocache(0, 1, HEOS.mole_fractions, tau, delta, Tc, rhoc);
1381 CoolPropDbl da0_dtau = _HEOS.calc_alpha0_deriv_nocache(1, 0, HEOS.mole_fractions, tau, delta, Tc, rhoc);
1382 CoolPropDbl d2a0_dtau2 = _HEOS.calc_alpha0_deriv_nocache(2, 0, HEOS.mole_fractions, tau, delta, Tc, rhoc);
1383 CoolPropDbl d2a0_ddelta_dtau = 0.0;
1384
1385 CoolPropDbl ar = _HEOS.calc_alphar_deriv_nocache(0, 0, HEOS.mole_fractions, tau, delta);
1386 CoolPropDbl dar_dtau = _HEOS.calc_alphar_deriv_nocache(1, 0, HEOS.mole_fractions, tau, delta);
1387 CoolPropDbl dar_ddelta = _HEOS.calc_alphar_deriv_nocache(0, 1, HEOS.mole_fractions, tau, delta);
1388 CoolPropDbl d2ar_ddelta_dtau = _HEOS.calc_alphar_deriv_nocache(1, 1, HEOS.mole_fractions, tau, delta);
1389 CoolPropDbl d2ar_ddelta2 = _HEOS.calc_alphar_deriv_nocache(0, 2, HEOS.mole_fractions, tau, delta);
1390 CoolPropDbl d2ar_dtau2 = _HEOS.calc_alphar_deriv_nocache(2, 0, HEOS.mole_fractions, tau, delta);
1391
1392 CoolPropDbl f1 = delta / tau * (1 + delta * dar_ddelta) - p / (rhoc * R * Tc);
1393 CoolPropDbl df1_dtau = (1 + delta * dar_ddelta) * (-delta / tau / tau) + delta / tau * (delta * d2ar_ddelta_dtau);
1394 CoolPropDbl df1_ddelta = (1.0 / tau) * (1 + 2.0 * delta * dar_ddelta + delta * delta * d2ar_ddelta2);
1395 switch (other) {
1396 case iHmolar: {
1397 f2 = (1 + delta * dar_ddelta) + tau * (da0_dtau + dar_dtau) - tau * y / (R * Tc);
1398 df2_dtau = delta * d2ar_ddelta_dtau + da0_dtau + dar_dtau + tau * (d2a0_dtau2 + d2ar_dtau2) - y / (R * Tc);
1399 df2_ddelta = (dar_ddelta + delta * d2ar_ddelta2) + tau * (d2a0_ddelta_dtau + d2ar_ddelta_dtau);
1400 break;
1401 }
1402 case iSmolar: {
1403 f2 = tau * (da0_dtau + dar_dtau) - ar - a0 - y / R;
1404 df2_dtau = tau * (d2a0_dtau2 + d2ar_dtau2) + (da0_dtau + dar_dtau) - dar_dtau - da0_dtau;
1405 df2_ddelta = tau * (d2a0_ddelta_dtau + d2ar_ddelta_dtau) - dar_ddelta - da0_ddelta;
1406 break;
1407 }
1408 default:
1409 throw ValueError("other variable in HSU_P_flash_singlephase_Newton is invalid");
1410 }
1411
1412 //First index is the row, second index is the column
1413 A[0][0] = df1_dtau;
1414 A[0][1] = df1_ddelta;
1415 A[1][0] = df2_dtau;
1416 A[1][1] = df2_ddelta;
1417
1418 //double det = A[0][0]*A[1][1]-A[1][0]*A[0][1];
1419
1420 MatInv_2(A, B);
1421 tau -= omega * (B[0][0] * f1 + B[0][1] * f2);
1422 delta -= omega * (B[1][0] * f1 + B[1][1] * f2);
1423
1424 if (std::abs(f1) > std::abs(f2))
1425 worst_error = std::abs(f1);
1426 else
1427 worst_error = std::abs(f2);
1428
1429 if (!ValidNumber(f1) || !ValidNumber(f2)) {
1430 throw SolutionError(format("Invalid values for inputs p=%g y=%g for fluid %s in HSU_P_flash_singlephase", p, y, _HEOS.name().c_str()));
1431 }
1432
1433 iter += 1;
1434 if (iter > 100) {
1435 throw SolutionError(format("HSU_P_flash_singlephase did not converge with inputs p=%g h=%g for fluid %s", p, y, _HEOS.name().c_str()));
1436 }
1437 }
1438
1439 HEOS.update(DmolarT_INPUTS, rhoc * delta, Tc / tau);
1440}
1442 CoolPropDbl Tmax, phases phase) {
1443 if (!ValidNumber(HEOS._p)) {
1444 throw ValueError("value for p in HSU_P_flash_singlephase_Brent is invalid");
1445 };
1446 if (!ValidNumber(value)) {
1447 throw ValueError("value for other in HSU_P_flash_singlephase_Brent is invalid");
1448 };
1449 class solver_resid : public FuncWrapper1DWithTwoDerivs
1450 {
1451 public:
1453 CoolPropDbl p, value;
1454 parameters other;
1455 int iter;
1456 CoolPropDbl eos0, eos1, rhomolar, rhomolar0, rhomolar1;
1457 CoolPropDbl Tmin, Tmax;
1458
1459 solver_resid(HelmholtzEOSMixtureBackend* HEOS, CoolPropDbl p, CoolPropDbl value, parameters other, double Tmin, double Tmax)
1460 : HEOS(HEOS),
1461 p(p),
1462 value(value),
1463 other(other),
1464 iter(0),
1465 eos0(-_HUGE),
1466 eos1(-_HUGE),
1467 rhomolar(_HUGE),
1468 rhomolar0(_HUGE),
1469 rhomolar1(_HUGE),
1470 Tmin(Tmin),
1471 Tmax(Tmax) {
1472 // Specify the state to avoid saturation calls, but only if phase is subcritical
1473 switch (CoolProp::phases phase = HEOS->phase()) {
1474 case iphase_liquid:
1475 case iphase_gas:
1476 HEOS->specify_phase(phase);
1477 default:
1478 // Otherwise don't do anything (this is to make compiler happy)
1479 {}
1480 }
1481 }
1482 double call(double T) {
1483
1484 if (iter < 2 || std::abs(rhomolar1 / rhomolar0 - 1) > 0.05) {
1485 // Run the solver with T,P as inputs; but only if the last change in density was greater than a few percent
1486 HEOS->update(PT_INPUTS, p, T);
1487 } else {
1488 // Run the solver with T,P as inputs; but use the guess value for density from before
1489 HEOS->update_TP_guessrho(T, p, rhomolar);
1490 }
1491
1492 // Get the value of the desired variable
1493 CoolPropDbl eos = HEOS->keyed_output(other);
1494
1495 // Store the value of density
1496 rhomolar = HEOS->rhomolar();
1497
1498 // Difference between the two is to be driven to zero
1499 CoolPropDbl r = eos - value;
1500
1501 // Store values for later use if there are errors
1502 if (iter == 0) {
1503 eos0 = eos;
1504 rhomolar0 = rhomolar;
1505 } else if (iter == 1) {
1506 eos1 = eos;
1507 rhomolar1 = rhomolar;
1508 } else {
1509 eos0 = eos1;
1510 eos1 = eos;
1511 rhomolar0 = rhomolar1;
1512 rhomolar1 = rhomolar;
1513 }
1514
1515 iter++;
1516 return r;
1517 };
1518 double deriv(double T) {
1519 return HEOS->first_partial_deriv(other, iT, iP);
1520 }
1521 double second_deriv(double T) {
1522 return HEOS->second_partial_deriv(other, iT, iP, iT, iP);
1523 }
1524 bool input_not_in_range(double x) {
1525 return (x < Tmin || x > Tmax);
1526 }
1527 };
1528 solver_resid resid(&HEOS, HEOS._p, value, other, Tmin, Tmax);
1529
1530 try {
1531 // First try to use Halley's method (including two derivatives)
1532 Halley(resid, Tmin, 1e-12, 100);
1533 if (!is_in_closed_range(Tmin, Tmax, static_cast<CoolPropDbl>(resid.HEOS->T())) || resid.HEOS->phase() != phase) {
1534 throw ValueError("Halley's method was unable to find a solution in HSU_P_flash_singlephase_Brent");
1535 }
1536 // Un-specify the phase of the fluid
1537 HEOS.unspecify_phase();
1538 } catch (...) {
1539 try {
1540 resid.iter = 0;
1541 // HEOS.unspecify_phase(); Tried to fix #2470, but this breaks a lot of other things
1542 // Halley's method failed, so now we try Brent's method
1543 Brent(resid, Tmin, Tmax, DBL_EPSILON, 1e-12, 100);
1544 // Un-specify the phase of the fluid
1545 HEOS.unspecify_phase();
1546 } catch (...) {
1547 // Un-specify the phase of the fluid
1548 HEOS.unspecify_phase();
1549
1550 // Determine why you were out of range if you can
1551 //
1552 CoolPropDbl eos0 = resid.eos0, eos1 = resid.eos1;
1553 std::string name = get_parameter_information(other, "short");
1554 std::string units = get_parameter_information(other, "units");
1555 if (eos1 > eos0 && value > eos1) {
1556 throw ValueError(
1557 format("HSU_P_flash_singlephase_Brent could not find a solution because %s [%Lg %s] is above the maximum value of %0.12Lg %s",
1558 name.c_str(), value, units.c_str(), eos1, units.c_str()));
1559 }
1560 if (eos1 > eos0 && value < eos0) {
1561 throw ValueError(
1562 format("HSU_P_flash_singlephase_Brent could not find a solution because %s [%Lg %s] is below the minimum value of %0.12Lg %s",
1563 name.c_str(), value, units.c_str(), eos0, units.c_str()));
1564 }
1565 throw;
1566 }
1567 }
1568}
1569
1570// P given and one of H, S, or U
1572 bool saturation_called = false;
1573 CoolPropDbl value;
1574
1575 // Find the phase, while updating all internal variables possible
1576 switch (other) {
1577 case iSmolar:
1578 value = HEOS.smolar();
1579 break;
1580 case iHmolar:
1581 value = HEOS.hmolar();
1582 break;
1583 case iUmolar:
1584 value = HEOS.umolar();
1585 break;
1586 default:
1587 throw ValueError(format("Input for other [%s] is invalid", get_parameter_information(other, "long").c_str()));
1588 }
1589 if (HEOS.is_pure_or_pseudopure) {
1590
1591 // Find the phase, while updating all internal variables possible
1592 HEOS.p_phase_determination_pure_or_pseudopure(other, value, saturation_called);
1593
1594 if (HEOS.isHomogeneousPhase()) {
1595 // Now we use the single-phase solver to find T,rho given P,Y using a
1596 // bounded 1D solver by adjusting T and using given value of p
1597 CoolPropDbl Tmin, Tmax;
1598 switch (HEOS._phase) {
1599 case iphase_gas: {
1600 Tmax = 1.5 * HEOS.Tmax();
1601 if (HEOS._p < HEOS.p_triple()) {
1602 Tmin = std::max(HEOS.Tmin(), HEOS.Ttriple());
1603 } else {
1604
1605 if (get_config_bool(ENABLE_SUPERANCILLARIES) && HEOS.is_pure()){
1606 auto& optsuperanc = HEOS.get_superanc_optional();
1607 if (optsuperanc){
1608 auto& superanc = optsuperanc.value();
1609 CoolPropDbl pmax_num = superanc.get_pmax();
1610 if (HEOS._p > pmax_num){
1611 throw ValueError(format("Pressure to PQ_flash [%0.8Lg Pa] may not be above the numerical critical point of %0.15Lg Pa", HEOS._p, pmax_num));
1612 }
1613 Tmin = superanc.get_T_from_p(HEOS._p)-1e-12;
1614 break;
1615 }
1616 }
1617 if (saturation_called) {
1618 Tmin = HEOS.SatV->T();
1619 } else {
1620 Tmin = HEOS._TVanc.pt() - 0.01;
1621 }
1622 }
1623 break;
1624 }
1625 case iphase_liquid: {
1626 if (saturation_called) {
1627 Tmax = HEOS.SatL->T();
1628 } else {
1629 Tmax = HEOS._TLanc.pt() + 0.01;
1630 }
1631
1632 // Sometimes the minimum pressure for the melting line is a bit above the triple point pressure
1633 if (HEOS.has_melting_line() && HEOS._p > HEOS.calc_melting_line(iP_min, -1, -1)) {
1634 Tmin = HEOS.calc_melting_line(iT, iP, HEOS._p) - 1e-3;
1635 } else {
1636 Tmin = HEOS.Tmin() - 1e-3;
1637 }
1638 break;
1639 }
1642 case iphase_supercritical: {
1643 Tmax = 1.5 * HEOS.Tmax();
1644 // Sometimes the minimum pressure for the melting line is a bit above the triple point pressure
1645 if (HEOS.has_melting_line() && HEOS._p > HEOS.calc_melting_line(iP_min, -1, -1)) {
1646 Tmin = HEOS.calc_melting_line(iT, iP, HEOS._p) - 1e-3;
1647 } else {
1648 Tmin = HEOS.Tmin() - 1e-3;
1649 }
1650 break;
1651 }
1652 default: {
1653 throw ValueError(format("Not a valid homogeneous state"));
1654 }
1655 }
1656 try {
1657 HSU_P_flash_singlephase_Brent(HEOS, other, value, Tmin, Tmax, HEOS._phase);
1658 } catch (std::exception& e) {
1659 throw ValueError(format("unable to solve 1phase PY flash with Tmin=%Lg, Tmax=%Lg due to error: %s", Tmin, Tmax, e.what()));
1660 }
1661 HEOS._Q = -1;
1662 // Update the state for conditions where the state was guessed
1664 }
1665 } else {
1666 if (HEOS.PhaseEnvelope.built) {
1667 // Determine whether you are inside or outside
1668 SimpleState closest_state;
1669 std::size_t iclosest;
1670 bool twophase = PhaseEnvelopeRoutines::is_inside(HEOS.PhaseEnvelope, iP, HEOS._p, other, value, iclosest, closest_state);
1671
1672 if (!twophase) {
1673 PY_singlephase_flash_resid resid(HEOS, HEOS._p, other, value);
1674 // If that fails, try a bounded solver
1675 Brent(resid, closest_state.T + 10, 1000, DBL_EPSILON, 1e-10, 100);
1676 HEOS.unspecify_phase();
1677 } else {
1678 throw ValueError("two-phase solution for Y");
1679 }
1680
1681 } else {
1682 throw ValueError("phase envelope must be built to carry out HSU_P_flash for mixture");
1683 }
1684 }
1685}
1687 // Define the residual to be driven to zero
1688 class solver_resid : public FuncWrapper1DWithTwoDerivs
1689 {
1690 public:
1692 CoolPropDbl T, value;
1693 parameters other;
1694
1695 solver_resid(HelmholtzEOSMixtureBackend* HEOS, CoolPropDbl T, CoolPropDbl value, parameters other)
1696 : HEOS(HEOS), T(T), value(value), other(other) {}
1697 double call(double rhomolar) {
1698 HEOS->update_DmolarT_direct(rhomolar, T);
1699 double eos = HEOS->keyed_output(other);
1700 return eos - value;
1701 };
1702 double deriv(double rhomolar) {
1703 return HEOS->first_partial_deriv(other, iDmolar, iT);
1704 }
1705 double second_deriv(double rhomolar) {
1706 return HEOS->second_partial_deriv(other, iDmolar, iT, iDmolar, iT);
1707 }
1708 };
1709 solver_resid resid(&HEOS, T, value, other);
1710
1711 // Supercritical temperature
1712 if (HEOS._T > HEOS._crit.T) {
1713 CoolPropDbl yc, ymin, y;
1714 CoolPropDbl rhoc = HEOS.components[0].crit.rhomolar;
1715 CoolPropDbl rhomin = 1e-10;
1716
1717 // Determine limits for the other variable
1718 switch (other) {
1719 case iSmolar: {
1720 yc = HEOS.calc_smolar_nocache(HEOS._T, rhoc);
1721 ymin = HEOS.calc_smolar_nocache(HEOS._T, rhomin);
1722 y = HEOS._smolar;
1723 break;
1724 }
1725 case iHmolar: {
1726 yc = HEOS.calc_hmolar_nocache(HEOS._T, rhoc);
1727 ymin = HEOS.calc_hmolar_nocache(HEOS._T, rhomin);
1728 y = HEOS._hmolar;
1729 break;
1730 }
1731 case iUmolar: {
1732 yc = HEOS.calc_umolar_nocache(HEOS._T, rhoc);
1733 ymin = HEOS.calc_umolar_nocache(HEOS._T, rhomin);
1734 y = HEOS._umolar;
1735 break;
1736 }
1737 default:
1738 throw ValueError();
1739 }
1740 if (is_in_closed_range(yc, ymin, y)) {
1741 Brent(resid, rhoc, rhomin, LDBL_EPSILON, 1e-9, 100);
1742 } else if (y < yc) {
1743 // Increase rhomelt until it bounds the solution
1744 int step_count = 0;
1745 while (!is_in_closed_range(ymin, yc, y)) {
1746 rhoc *= 1.1; // Increase density by a few percent
1747 switch (other) {
1748 case iSmolar:
1749 yc = HEOS.calc_smolar_nocache(HEOS._T, rhoc);
1750 break;
1751 case iHmolar:
1752 yc = HEOS.calc_hmolar_nocache(HEOS._T, rhoc);
1753 break;
1754 case iUmolar:
1755 yc = HEOS.calc_umolar_nocache(HEOS._T, rhoc);
1756 break;
1757 default:
1758 throw ValueError(format("Input is invalid"));
1759 }
1760 if (step_count > 30) {
1761 throw ValueError(format("Even by increasing rhoc, not able to bound input; input %Lg is not in range %Lg,%Lg", y, yc, ymin));
1762 }
1763 step_count++;
1764 }
1765 Brent(resid, rhomin, rhoc, LDBL_EPSILON, 1e-9, 100);
1766 } else {
1767 throw ValueError(format("input %Lg is not in range %Lg,%Lg,%Lg", y, yc, ymin));
1768 }
1769 // Update the state (T > Tc)
1770 if (HEOS._p < HEOS.p_critical()) {
1772 } else {
1774 }
1775 }
1776 // Subcritical temperature liquid
1777 else if ((HEOS._phase == iphase_liquid) || (HEOS._phase == iphase_supercritical_liquid)) {
1778 CoolPropDbl ymelt, yL, y;
1779 CoolPropDbl rhomelt = HEOS.components[0].triple_liquid.rhomolar;
1780 CoolPropDbl rhoL = static_cast<double>(HEOS._rhoLanc);
1781
1782 switch (other) {
1783 case iSmolar: {
1784 ymelt = HEOS.calc_smolar_nocache(HEOS._T, rhomelt);
1785 yL = HEOS.calc_smolar_nocache(HEOS._T, rhoL);
1786 y = HEOS._smolar;
1787 break;
1788 }
1789 case iHmolar: {
1790 ymelt = HEOS.calc_hmolar_nocache(HEOS._T, rhomelt);
1791 yL = HEOS.calc_hmolar_nocache(HEOS._T, rhoL);
1792 y = HEOS._hmolar;
1793 break;
1794 }
1795 case iUmolar: {
1796 ymelt = HEOS.calc_umolar_nocache(HEOS._T, rhomelt);
1797 yL = HEOS.calc_umolar_nocache(HEOS._T, rhoL);
1798 y = HEOS._umolar;
1799 break;
1800 }
1801 default:
1802 throw ValueError();
1803 }
1804
1805 CoolPropDbl rhomolar_guess = (rhomelt - rhoL) / (ymelt - yL) * (y - yL) + rhoL;
1806
1807 try {
1808 Halley(resid, rhomolar_guess, 1e-8, 100);
1809 } catch (...) {
1810 Secant(resid, rhomolar_guess, 0.0001 * rhomolar_guess, 1e-12, 100);
1811 }
1812 }
1813 // Subcritical temperature gas
1814 else if (HEOS._phase == iphase_gas) {
1815 CoolPropDbl rhomin = 1e-14;
1816 CoolPropDbl rhoV = static_cast<double>(HEOS._rhoVanc);
1817
1818 try {
1819 Halley(resid, 0.5 * (rhomin + rhoV), 1e-8, 100);
1820 } catch (...) {
1821 try {
1822 Brent(resid, rhomin, rhoV, LDBL_EPSILON, 1e-12, 100);
1823 } catch (...) {
1824 throw ValueError();
1825 }
1826 }
1827 } else {
1828 throw ValueError(format("phase to solver_for_rho_given_T_oneof_HSU is invalid"));
1829 }
1830};
1831
1834 // Use the phase defined by the imposed phase
1835 HEOS._phase = HEOS.imposed_phase_index;
1836 // The remaining code in this branch was added to set some needed parameters if phase is imposed,
1837 // since HEOS.T_phase_determination_pure_or_pseudopure() is not being called.
1838 if (HEOS._T < HEOS._crit.T) //
1839 {
1840 HEOS._rhoVanc = HEOS.components[0].ancillaries.rhoV.evaluate(HEOS._T);
1841 HEOS._rhoLanc = HEOS.components[0].ancillaries.rhoL.evaluate(HEOS._T);
1842 if (HEOS._phase == iphase_liquid) {
1843 HEOS._Q = -1000;
1844 } else if (HEOS._phase == iphase_gas) {
1845 HEOS._Q = 1000;
1846 } else if (HEOS._phase == iphase_twophase) {
1847 // Actually have to use saturation information sadly
1848 // For the given temperature, find the saturation state
1849 // Run the saturation routines to determine the saturation densities and pressures
1852 SaturationSolvers::saturation_T_pure(HEOS1, HEOS._T, options);
1853
1854 if (other != iDmolar) {
1855 // Update the states
1856 if (HEOS.SatL) HEOS.SatL->update(DmolarT_INPUTS, HEOS._rhoLanc, HEOS._T);
1857 if (HEOS.SatV) HEOS.SatV->update(DmolarT_INPUTS, HEOS._rhoVanc, HEOS._T);
1858 // Update the two-Phase variables
1859 HEOS._rhoLmolar = HEOS.SatL->rhomolar();
1860 HEOS._rhoVmolar = HEOS.SatV->rhomolar();
1861 }
1862
1863 CoolPropDbl Q;
1864
1865 switch (other) {
1866 case iDmolar:
1867 Q = (1 / HEOS.rhomolar() - 1 / HEOS1.SatL->rhomolar()) / (1 / HEOS1.SatV->rhomolar() - 1 / HEOS1.SatL->rhomolar());
1868 break;
1869 case iSmolar:
1870 Q = (HEOS.smolar() - HEOS1.SatL->smolar()) / (HEOS1.SatV->smolar() - HEOS1.SatL->smolar());
1871 break;
1872 case iHmolar:
1873 Q = (HEOS.hmolar() - HEOS1.SatL->hmolar()) / (HEOS1.SatV->hmolar() - HEOS1.SatL->hmolar());
1874 break;
1875 case iUmolar:
1876 Q = (HEOS.umolar() - HEOS1.SatL->umolar()) / (HEOS1.SatV->umolar() - HEOS1.SatL->umolar());
1877 break;
1878 default:
1879 throw ValueError(format("bad input for other"));
1880 }
1881 if (Q < 0) {
1882 HEOS._Q = -1;
1883 } else if (Q > 1) {
1884 HEOS._Q = 1;
1885 } else {
1886 HEOS._Q = Q;
1887 // Load the outputs
1888 HEOS._p = HEOS._Q * HEOS1.SatV->p() + (1 - HEOS._Q) * HEOS1.SatL->p();
1889 HEOS._rhomolar = 1 / (HEOS._Q / HEOS.SatV->rhomolar() + (1 - HEOS._Q) / HEOS.SatL->rhomolar());
1890 }
1891 } else if (HEOS._phase == iphase_supercritical_liquid) {
1892 HEOS._Q = -1000;
1893 } else
1894 throw ValueError(format("Temperature specified is not the imposed phase region."));
1895 } else if (HEOS._T > HEOS._crit.T && HEOS._T > HEOS.components[0].EOS().Ttriple) {
1896 HEOS._Q = 1e9;
1897 }
1898 } else {
1899 if (HEOS.is_pure_or_pseudopure) {
1900 // Find the phase, while updating all internal variables possible
1901 switch (other) {
1902 case iDmolar:
1904 break;
1905 case iSmolar:
1907 break;
1908 case iHmolar:
1910 break;
1911 case iUmolar:
1913 break;
1914 default:
1915 throw ValueError(format("Input is invalid"));
1916 }
1917 } else {
1918 HEOS._phase = iphase_gas;
1919 throw NotImplementedError("DHSU_T_flash does not support mixtures (yet)");
1920 }
1921 }
1922
1923 //if (HEOS.isHomogeneousPhase() && !ValidNumber(HEOS._p)) // original, pre 1352
1924 // only the solver requires single phase
1925 if (((other == iDmolar) || HEOS.isHomogeneousPhase()) && !ValidNumber(HEOS._p)) // post 1352
1926 {
1927 switch (other) {
1928 case iDmolar:
1929 break;
1930 case iHmolar:
1932 break;
1933 case iSmolar:
1935 break;
1936 case iUmolar:
1938 break;
1939 default:
1940 break;
1941 }
1942 HEOS.calc_pressure();
1943 HEOS._Q = -1;
1944 }
1945 if (HEOS.is_pure_or_pseudopure && HEOS._phase != iphase_twophase) {
1946 // Update the state for conditions where the state was guessed
1948 }
1949}
1951 HS_flash_twophaseOptions& options) {
1952 class Residual : public FuncWrapper1D
1953 {
1954
1955 public:
1957 CoolPropDbl hmolar, smolar, Qs;
1958 Residual(HelmholtzEOSMixtureBackend& HEOS, CoolPropDbl hmolar_spec, CoolPropDbl smolar_spec)
1959 : HEOS(HEOS), hmolar(hmolar_spec), smolar(smolar_spec), Qs(_HUGE){};
1960 double call(double T) {
1961 HEOS.update(QT_INPUTS, 0, T);
1962 HelmholtzEOSMixtureBackend &SatL = HEOS.get_SatL(), &SatV = HEOS.get_SatV();
1963 // Quality from entropy
1964 Qs = (smolar - SatL.smolar()) / (SatV.smolar() - SatL.smolar());
1965 // Quality from enthalpy
1966 CoolPropDbl Qh = (hmolar - SatL.hmolar()) / (SatV.hmolar() - SatL.hmolar());
1967 // Residual is the difference between the two
1968 return Qh - Qs;
1969 }
1970 } resid(HEOS, hmolar_spec, smolar_spec);
1971
1972 // Critical point for pure fluids, slightly different for pseudo-pure, very different for mixtures
1973 CoolPropDbl Tmax_sat = HEOS.calc_Tmax_sat() - 1e-13;
1974
1975 // Check what the minimum limits for the equation of state are
1976 CoolPropDbl Tmin_satL, Tmin_satV, Tmin_sat;
1977 HEOS.calc_Tmin_sat(Tmin_satL, Tmin_satV);
1978 Tmin_sat = std::max(Tmin_satL, Tmin_satV) - 1e-13;
1979
1980 Brent(resid, Tmin_sat, Tmax_sat - 0.01, DBL_EPSILON, 1e-12, 20);
1981 // Run once more with the final vapor quality
1982 HEOS.update(QT_INPUTS, resid.Qs, HEOS.T());
1983}
1985 HS_flash_singlephaseOptions& options) {
1986 int iter = 0;
1987 double resid = 9e30, resid_old = 9e30;
1988 CoolProp::SimpleState reducing = HEOS.get_state("reducing");
1989 do {
1990 // Independent variables are T0 and rhomolar0, residuals are matching h and s
1991 Eigen::Vector2d r;
1992 Eigen::Matrix2d J;
1993 r(0) = HEOS.hmolar() - hmolar_spec;
1994 r(1) = HEOS.smolar() - smolar_spec;
1995 J(0, 0) = HEOS.first_partial_deriv(iHmolar, iTau, iDelta);
1996 J(0, 1) = HEOS.first_partial_deriv(iHmolar, iDelta, iTau);
1997 J(1, 0) = HEOS.first_partial_deriv(iSmolar, iTau, iDelta);
1998 J(1, 1) = HEOS.first_partial_deriv(iSmolar, iDelta, iTau);
1999 // Step in v obtained from Jv = -r
2000 Eigen::Vector2d v = J.colPivHouseholderQr().solve(-r);
2001 bool good_solution = false;
2002 double tau0 = HEOS.tau(), delta0 = HEOS.delta();
2003 // Calculate the old residual after the last step
2004 resid_old = sqrt(POW2(HEOS.hmolar() - hmolar_spec) + POW2(HEOS.smolar() - smolar_spec));
2005 for (double frac = 1.0; frac > 0.001; frac /= 2) {
2006 try {
2007 // Calculate new values
2008 double tau_new = tau0 + options.omega * frac * v(0);
2009 double delta_new = delta0 + options.omega * frac * v(1);
2010 double T_new = reducing.T / tau_new;
2011 double rhomolar_new = delta_new * reducing.rhomolar;
2012 // Update state with step
2013 HEOS.update(DmolarT_INPUTS, rhomolar_new, T_new);
2014 resid = sqrt(POW2(HEOS.hmolar() - hmolar_spec) + POW2(HEOS.smolar() - smolar_spec));
2015 if (resid > resid_old) {
2016 throw ValueError(format("residual not decreasing; frac: %g, resid: %g, resid_old: %g", frac, resid, resid_old));
2017 }
2018 good_solution = true;
2019 break;
2020 } catch (...) {
2021 HEOS.clear();
2022 continue;
2023 }
2024 }
2025 if (!good_solution) {
2026 throw ValueError(format("Not able to get a solution"));
2027 }
2028 iter++;
2029 if (iter > 50) {
2030 throw ValueError(format("HS_flash_singlephase took too many iterations; residual is %g; prior was %g", resid, resid_old));
2031 }
2032 } while (std::abs(resid) > 1e-9);
2033}
2035 // Randomly obtain a starting value that is single-phase
2036 double logp = ((double)rand() / (double)RAND_MAX) * (log(HEOS.pmax()) - log(HEOS.p_triple())) + log(HEOS.p_triple());
2037 T = ((double)rand() / (double)RAND_MAX) * (HEOS.Tmax() - HEOS.Ttriple()) + HEOS.Ttriple();
2038 p = exp(logp);
2039}
2041 // Use TS flash and iterate on T (known to be between Tmin and Tmax)
2042 // in order to find H
2043 double hmolar = HEOS.hmolar(), smolar = HEOS.smolar();
2044 class Residual : public FuncWrapper1D
2045 {
2046 public:
2048 CoolPropDbl hmolar, smolar;
2049 Residual(HelmholtzEOSMixtureBackend& HEOS, CoolPropDbl hmolar_spec, CoolPropDbl smolar_spec)
2050 : HEOS(HEOS), hmolar(hmolar_spec), smolar(smolar_spec){};
2051 double call(double T) {
2052 HEOS.update(SmolarT_INPUTS, smolar, T);
2053 double r = HEOS.hmolar() - hmolar;
2054 return r;
2055 }
2056 } resid(HEOS, hmolar, smolar);
2057 std::string errstr;
2058 // Find minimum temperature
2059 bool good_Tmin = false;
2060 double Tmin = HEOS.Ttriple();
2061 double rmin;
2062 do {
2063 try {
2064 rmin = resid.call(Tmin);
2065 good_Tmin = true;
2066 } catch (...) {
2067 Tmin += 0.5;
2068 }
2069 if (Tmin > HEOS.Tmax()) {
2070 throw ValueError("Cannot find good Tmin");
2071 }
2072 } while (!good_Tmin);
2073
2074 // Find maximum temperature
2075 bool good_Tmax = false;
2076 double Tmax = HEOS.Tmax() * 1.01; // Just a little above, so if we use Tmax as input, it should still work
2077 double rmax;
2078 do {
2079 try {
2080 rmax = resid.call(Tmax);
2081 good_Tmax = true;
2082 } catch (...) {
2083 Tmax -= 0.1;
2084 }
2085 if (Tmax < Tmin) {
2086 throw ValueError("Cannot find good Tmax");
2087 }
2088 } while (!good_Tmax);
2089 if (rmin * rmax > 0 && std::abs(rmax) < std::abs(rmin)) {
2090 throw CoolProp::ValueError(format("HS inputs correspond to temperature above maximum temperature of EOS [%g K]", HEOS.Tmax()));
2091 }
2092 Brent(resid, Tmin, Tmax, DBL_EPSILON, 1e-10, 100);
2093}
2094
2095#if defined(ENABLE_CATCH)
2096
2097TEST_CASE("PD with T very large should yield error", "[PDflash]") {
2098 shared_ptr<HelmholtzEOSBackend> HEOS(new HelmholtzEOSBackend("R134a"));
2099 double Tc = HEOS->T_critical();
2100 HEOS->update(DmassT_INPUTS, 1.1, 1.5 * Tc);
2101 CHECK_THROWS(HEOS->update(DmassP_INPUTS, 2, 5 * HEOS->p()));
2102}
2103
2104TEST_CASE("Stability testing", "[stability]") {
2105 shared_ptr<HelmholtzEOSMixtureBackend> HEOS(new HelmholtzEOSMixtureBackend(strsplit("n-Propane&n-Butane&n-Pentane&n-Hexane", '&')));
2106 std::vector<double> z(4);
2107 z[0] = 0.1;
2108 z[1] = 0.2;
2109 z[2] = 0.3;
2110 z[3] = 0.4;
2111 HEOS->set_mole_fractions(z);
2112
2113 HEOS->update(PQ_INPUTS, 101325, 0);
2114 double TL = HEOS->T();
2115
2116 HEOS->update(PQ_INPUTS, 101325, 1);
2117 double TV = HEOS->T();
2118
2119 SECTION("Liquid (feed is stable)") {
2120 StabilityRoutines::StabilityEvaluationClass stability_tester(*HEOS);
2121 for (double T = TL - 1; T >= 100; T -= 1) {
2122 stability_tester.set_TP(T, 101325);
2123 CAPTURE(T);
2124 CHECK_NOTHROW(stability_tester.is_stable());
2125 }
2126 }
2127 SECTION("Vapor (feed is stable)") {
2128 StabilityRoutines::StabilityEvaluationClass stability_tester(*HEOS);
2129 for (double T = TV + 1; T <= 500; T += 1) {
2130 stability_tester.set_TP(T, 101325);
2131 CAPTURE(T);
2132 CHECK_NOTHROW(stability_tester.is_stable());
2133 }
2134 }
2135 SECTION("Two-phase (feed is unstable)") {
2136 StabilityRoutines::StabilityEvaluationClass stability_tester(*HEOS);
2137 stability_tester.set_TP((TV + TL) / 2.0, 101325);
2138 CHECK(stability_tester.is_stable() == false);
2139 }
2140}
2141
2142TEST_CASE("Test critical points for methane + H2S", "[critical_points]") {
2143 shared_ptr<HelmholtzEOSMixtureBackend> HEOS(new HelmholtzEOSMixtureBackend(strsplit("Methane&H2S", '&')));
2144
2145 double zz[] = {0.998, 0.97, 0.9475, 0.94, 0.93, 0.86, 0.85, 0.84, 0.75, 0.53, 0.52, 0.51, 0.49, 0.36, 0.24, 0.229, 0.09};
2146 int Npts[] = {2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 1, 1, 1, 1}; // Number of critical points that should be found
2147 int imax = sizeof(zz) / sizeof(double);
2148
2149 for (int i = 0; i < imax; ++i) {
2150 double z0 = zz[i];
2151 std::vector<double> z(2);
2152 z[0] = z0;
2153 z[1] = 1 - z0;
2154 HEOS->set_mole_fractions(z);
2155 CAPTURE(z0);
2156 std::vector<CriticalState> pts = HEOS->all_critical_points();
2157 CHECK(pts.size() == Npts[i]);
2158 }
2159}
2160
2161TEST_CASE("Test critical points for nitrogen + ethane with HEOS", "[critical_points]") {
2162 shared_ptr<HelmholtzEOSMixtureBackend> HEOS(new HelmholtzEOSMixtureBackend(strsplit("Nitrogen&Ethane", '&')));
2163 std::vector<double> zz = linspace(0.001, 0.999, 21);
2164 int failure_count = 0;
2165 for (int i = 0; i < static_cast<std::size_t>(zz.size()); ++i) {
2166 double z0 = zz[i];
2167 std::vector<double> z(2);
2168 z[0] = z0;
2169 z[1] = 1 - z0;
2170 HEOS->set_mole_fractions(z);
2171 CAPTURE(z0);
2172 std::vector<CriticalState> pts;
2173 try{
2174 pts = HEOS->all_critical_points();
2175 }
2176 catch(std::exception& e){
2177 CAPTURE(e.what());
2178 failure_count++;
2179 }
2180 }
2181 // Only an error if more than half fail;
2182 CHECK(failure_count < 10);
2183}
2184
2185TEST_CASE("Test critical points for nitrogen + ethane with PR", "[critical_points]") {
2186 shared_ptr<PengRobinsonBackend> HEOS(new PengRobinsonBackend(strsplit("Nitrogen&Ethane", '&')));
2187 HEOS->set_binary_interaction_double(0, 1, "kij", 0.0407); // Ramırez-Jimenez et al.
2188 std::vector<double> zz = linspace(0.001, 0.999, 21);
2189 for (int i = 0; i < static_cast<std::size_t>(zz.size()); ++i) {
2190 double z0 = zz[i];
2191 std::vector<double> z(2);
2192 z[0] = z0;
2193 z[1] = 1 - z0;
2194 HEOS->set_mole_fractions(z);
2195 CAPTURE(z0);
2196 std::vector<CriticalState> pts;
2197 CHECK_NOTHROW(pts = HEOS->all_critical_points());
2198 }
2199}
2200
2201#endif
2202
2203} /* namespace CoolProp */