CoolProp 6.8.0
An open-source fluid property and humid air property database
PhaseEnvelopeRoutines.cpp
Go to the documentation of this file.
1#ifndef PHASEENVELOPE_H
2#define PHASEENVELOPE_H
3
5#include "VLERoutines.h"
7#include "PhaseEnvelope.h"
8#include "CoolPropTools.h"
9#include "Configuration.h"
10#include "CPnumerics.h"
11
12namespace CoolProp {
13
14void PhaseEnvelopeRoutines::build(HelmholtzEOSMixtureBackend& HEOS, const std::string& level) {
15 if (HEOS.get_mole_fractions_ref().empty()) {
16 throw ValueError("Mole fractions have not been set yet.");
17 }
18 bool debug = get_debug_level() > 0 || false;
19 if (HEOS.get_mole_fractions_ref().size() == 1) {
20 // It's a pure fluid
22 env.resize(HEOS.mole_fractions.size());
23
24 // Breakpoints in the phase envelope
25 std::vector<CoolPropDbl> Tbp, Qbp;
26 std::vector<std::size_t> Nbp;
27 // Triple point vapor up to Tmax_sat
28 Tbp.push_back(HEOS.Ttriple());
29 Qbp.push_back(1.0);
30 Nbp.push_back(40);
31
32 if (HEOS.is_pure()) {
33 // Up to critical point, back to triple point on the liquid side
34 Tbp.push_back(HEOS.T_critical() - 1e-3);
35 Qbp.push_back(0.0);
36 Tbp.push_back(HEOS.Ttriple());
37 Nbp.push_back(40);
38 } else {
39 SimpleState max_sat_T = HEOS.get_state("max_sat_T"), max_sat_p = HEOS.get_state("max_sat_p"), crit = HEOS.get_state("critical");
40 if (max_sat_T.rhomolar < crit.rhomolar && max_sat_T.rhomolar < max_sat_p.rhomolar) {
41 Tbp.push_back(HEOS.calc_Tmax_sat());
42 if (max_sat_p.rhomolar < crit.rhomolar) {
43 // psat_max density less than critical density
44 Qbp.push_back(1.0);
45 Qbp.push_back(1.0);
46 Tbp.push_back(max_sat_p.T);
47 Tbp.push_back(crit.T);
48 } else {
49 // Vapor line density less than critical density
50 Qbp.push_back(1.0);
51 Qbp.push_back(0.0);
52 Nbp.push_back(10);
53 Tbp.push_back(crit.T);
54 Tbp.push_back(max_sat_p.T);
55 }
56 Nbp.push_back(10);
57 Nbp.push_back(10);
58 Qbp.push_back(0.0);
59 Nbp.push_back(40);
60 Tbp.push_back(HEOS.Ttriple());
61 } else {
62 throw ValueError(format(""));
63 }
64 }
65
66 for (std::size_t i = 0; i < Tbp.size() - 1; ++i) {
67 CoolPropDbl Tmin = Tbp[i], Tmax = Tbp[i + 1];
68 std::size_t N = Nbp[i];
69 for (CoolPropDbl T = Tmin; is_in_closed_range(Tmin, Tmax, T); T += (Tmax - Tmin) / (N - 1)) {
70 try {
71 HEOS.update(QT_INPUTS, Qbp[i], T);
72 } catch (...) {
73 continue;
74 }
75 if (Qbp[i] > 0.5) {
79 std::vector<CoolPropDbl>(1, 1.0), std::vector<CoolPropDbl>(1, 1.0));
80 } else {
84 std::vector<CoolPropDbl>(1, 1.0), std::vector<CoolPropDbl>(1, 1.0));
85 }
86 }
87 }
88 } else {
89 // It's a mixture
90 // --------------
91
92 // First we try to generate all the critical points. This
93 // is very useful
94 std::vector<CriticalState> critpts;
95 // try{
96 // critpts = HEOS.all_critical_points();
97 // //throw CoolProp::ValueError("critical points disabled");
98 // }
99 // catch(std::exception &e)
100 // {
101 // if (debug){ std::cout << e.what() << std::endl; }
102 // };
103
104 std::size_t failure_count = 0;
105 // Set some input options
108 io.Nstep_max = 20;
109
110 // Set the pressure to a low pressure
111 HEOS._p = get_config_double(PHASE_ENVELOPE_STARTING_PRESSURE_PA); //[Pa]
112 HEOS._Q = 1;
113
114 // Get an extremely rough guess by interpolation of ln(p) v. T curve where the limits are mole-fraction-weighted
116
117 // Use Wilson iteration to obtain updated guess for temperature
118 Tguess = SaturationSolvers::saturation_Wilson(HEOS, HEOS._Q, HEOS._p, SaturationSolvers::imposed_p, HEOS.mole_fractions, Tguess);
119
120 // Actually call the successive substitution solver
121 io.beta = 1;
122 SaturationSolvers::successive_substitution(HEOS, HEOS._Q, Tguess, HEOS._p, HEOS.mole_fractions, HEOS.K, io);
123
124 // Use the residual function based on x_i, T and rho' as independent variables. rho'' is specified
125 SaturationSolvers::newton_raphson_saturation NR;
126 SaturationSolvers::newton_raphson_saturation_options IO;
127
128 IO.bubble_point = false; // Do a "dewpoint" calculation all the way around
129 IO.x = io.x;
130 IO.y = HEOS.mole_fractions;
131 IO.rhomolar_liq = io.rhomolar_liq;
132 IO.rhomolar_vap = io.rhomolar_vap;
133 IO.T = io.T;
134 IO.p = io.p;
135 IO.Nstep_max = 30;
136
137 /*
138 IO.p = 1e5;
139 IO.rhomolar_liq = 17257.17130;
140 IO.rhomolar_vap = 56.80022884;
141 IO.T = 219.5200523;
142 IO.x[0] = 0.6689704673;
143 IO.x[1] = 0.3310295327;
144 */
145
146 //IO.rhomolar_liq *= 1.2;
147
148 IO.imposed_variable = SaturationSolvers::newton_raphson_saturation_options::P_IMPOSED;
149
150 NR.call(HEOS, IO.y, IO.x, IO);
151
152 // Switch to density imposed
153 IO.imposed_variable = SaturationSolvers::newton_raphson_saturation_options::RHOV_IMPOSED;
154
155 bool dont_extrapolate = false;
156
158 env.resize(HEOS.mole_fractions.size());
159
160 std::size_t iter = 0, //< The iteration counter
161 iter0 = 0; //< A reference point for the counter, can be increased to go back to linear interpolation
162 CoolPropDbl factor = 1.05;
163
164 for (;;) {
165 top_of_loop:; // A goto label so that nested loops can break out to the top of this loop
166
167 if (failure_count > 5) {
168 // Stop since we are stuck at a bad point
169 //throw SolutionError("stuck");
170 return;
171 }
172
173 if (iter - iter0 > 0) {
174 IO.rhomolar_vap *= factor;
175 }
176 if (dont_extrapolate) {
177 // Reset the step to a reasonably small size
178 factor = 1.0001;
179 } else if (iter - iter0 == 2) {
180 IO.T = LinearInterp(env.rhomolar_vap, env.T, iter - 2, iter - 1, IO.rhomolar_vap);
181 IO.rhomolar_liq = LinearInterp(env.rhomolar_vap, env.rhomolar_liq, iter - 2, iter - 1, IO.rhomolar_vap);
182 for (std::size_t i = 0; i < IO.x.size() - 1; ++i) // First N-1 elements
183 {
184 IO.x[i] = LinearInterp(env.rhomolar_vap, env.x[i], iter - 2, iter - 1, IO.rhomolar_vap);
185 }
186 } else if (iter - iter0 == 3) {
187 IO.T = QuadInterp(env.rhomolar_vap, env.T, iter - 3, iter - 2, iter - 1, IO.rhomolar_vap);
188 IO.rhomolar_liq = QuadInterp(env.rhomolar_vap, env.rhomolar_liq, iter - 3, iter - 2, iter - 1, IO.rhomolar_vap);
189 for (std::size_t i = 0; i < IO.x.size() - 1; ++i) // First N-1 elements
190 {
191 IO.x[i] = QuadInterp(env.rhomolar_vap, env.x[i], iter - 3, iter - 2, iter - 1, IO.rhomolar_vap);
192 }
193 } else if (iter - iter0 > 3) {
194 // Use the spline interpolation class of Devin Lane: http://shiftedbits.org/2011/01/30/cubic-spline-interpolation/
195 Spline<double, double> spl_T(env.rhomolar_vap, env.T);
196 IO.T = spl_T.interpolate(IO.rhomolar_vap);
197 Spline<double, double> spl_rho(env.rhomolar_vap, env.rhomolar_liq);
198 IO.rhomolar_liq = spl_rho.interpolate(IO.rhomolar_vap);
199
200 // Check if there is a large deviation from linear interpolation - this suggests a step size that is so large that a minima or maxima of the interpolation function is crossed
201 CoolPropDbl T_linear = LinearInterp(env.rhomolar_vap, env.T, iter - 2, iter - 1, IO.rhomolar_vap);
202 if (std::abs((T_linear - IO.T) / IO.T) > 0.1) {
203 // Try again, but with a smaller step
204 IO.rhomolar_vap /= factor;
205 factor = 1 + (factor - 1) / 2;
206 failure_count++;
207 continue;
208 }
209 for (std::size_t i = 0; i < IO.x.size() - 1; ++i) // First N-1 elements
210 {
211 // Use the spline interpolation class of Devin Lane: http://shiftedbits.org/2011/01/30/cubic-spline-interpolation/
212 Spline<double, double> spl(env.rhomolar_vap, env.x[i]);
213 IO.x[i] = spl.interpolate(IO.rhomolar_vap);
214
215 if (IO.x[i] < 0 || IO.x[i] > 1) {
216 // Try again, but with a smaller step
217 IO.rhomolar_vap /= factor;
218 factor = 1 + (factor - 1) / 2;
219 failure_count++;
220 goto top_of_loop;
221 }
222 }
223 }
224
225 // The last mole fraction is sum of N-1 first elements
226 IO.x[IO.x.size() - 1] = 1 - std::accumulate(IO.x.begin(), IO.x.end() - 1, 0.0);
227
228 // Uncomment to check guess values for Newton-Raphson
229 //std::cout << "\t\tdv " << IO.rhomolar_vap << " dl " << IO.rhomolar_liq << " T " << IO.T << " x " << vec_to_string(IO.x, "%0.10Lg") << std::endl;
230
231 // Dewpoint calculation, liquid (x) is incipient phase
232 try {
233 NR.call(HEOS, IO.y, IO.x, IO);
234 if (!ValidNumber(IO.rhomolar_liq) || !ValidNumber(IO.p) || !ValidNumber(IO.T)) {
235 throw ValueError("Invalid number");
236 }
237 // Reject trivial solution
238 if (std::abs(IO.rhomolar_liq - IO.rhomolar_vap) < 1e-3) {
239 throw ValueError("Trivial solution");
240 }
241 // Reject negative presssure
242 if (IO.p < 0) {
243 throw ValueError("negative pressure");
244 }
245 // Reject steps with enormous steps in temperature
246 if (!env.T.empty() && std::abs(env.T[env.T.size() - 1] - IO.T) > 100) {
247 throw ValueError("Change in temperature too large");
248 }
249 } catch (std::exception& e) {
250 if (debug) {
251 std::cout << e.what() << std::endl;
252 }
253 //std::cout << IO.T << " " << IO.p << std::endl;
254 // Try again, but with a smaller step
255 IO.rhomolar_vap /= factor;
256 if (iter < 4) {
257 throw ValueError(format("Unable to calculate at least 4 points in phase envelope; quitting"));
258 }
259 IO.rhomolar_liq = QuadInterp(env.rhomolar_vap, env.rhomolar_liq, iter - 3, iter - 2, iter - 1, IO.rhomolar_vap);
260 factor = 1 + (factor - 1) / 2;
261 failure_count++;
262 continue;
263 }
264
265 if (debug) {
266 std::cout << "dv " << IO.rhomolar_vap << " dl " << IO.rhomolar_liq << " T " << IO.T << " p " << IO.p << " hl " << IO.hmolar_liq
267 << " hv " << IO.hmolar_vap << " sl " << IO.smolar_liq << " sv " << IO.smolar_vap << " x " << vec_to_string(IO.x, "%0.10Lg")
268 << " Ns " << IO.Nsteps << " factor " << factor << std::endl;
269 }
270 env.store_variables(IO.T, IO.p, IO.rhomolar_liq, IO.rhomolar_vap, IO.hmolar_liq, IO.hmolar_vap, IO.smolar_liq, IO.smolar_vap, IO.x, IO.y);
271
272 iter++;
273
274 // CoolPropDbl abs_rho_difference = std::abs((IO.rhomolar_liq - IO.rhomolar_vap)/IO.rhomolar_liq);
275
276 // bool next_crosses_crit = false;
277 // if (it_critpts != critpts.end() ){
278 // // Density at the next critical point
279 // double rhoc = (*it_critpts).rhomolar;
280 // // Next vapor density that will be used
281 // double rho_next = IO.rhomolar_vap*factor;
282 // // If the signs of the differences are different, you have crossed
283 // // the critical point density and have a phase inversion
284 // // on your hands
285 // next_crosses_crit = ((IO.rhomolar_vap-rhoc)*(rho_next-rhoc) < 0);
286 // }
287
288 // // Critical point jump
289 // if (next_crosses_crit || (abs_rho_difference < 0.01 && IO.rhomolar_liq > IO.rhomolar_vap)){
290 // //std::cout << "dv" << IO.rhomolar_vap << " dl " << IO.rhomolar_liq << " " << vec_to_string(IO.x, "%0.10Lg") << " " << vec_to_string(IO.y, "%0.10Lg") << std::endl;
291 // CoolPropDbl rhoc_approx = 0.5*IO.rhomolar_liq + 0.5*IO.rhomolar_vap;
292 // if (it_critpts != critpts.end() ){
293 // // We actually know what the critical point is to numerical precision
294 // rhoc_approx = (*it_critpts).rhomolar;
295 // }
296 // CoolPropDbl rho_vap_new = 1.05*rhoc_approx;
297 // // Linearly interpolate to get new guess for T
298 // IO.T = LinearInterp(env.rhomolar_vap,env.T,iter-2,iter-1,rho_vap_new);
299 // IO.rhomolar_liq = LinearInterp(env.rhomolar_vap, env.rhomolar_liq, iter-2, iter-1, rho_vap_new);
300 // for (std::size_t i = 0; i < IO.x.size()-1; ++i){
301 // IO.x[i] = CubicInterp(env.rhomolar_vap, env.x[i], iter-4, iter-3, iter-2, iter-1, rho_vap_new);
302 // }
303 // IO.x[IO.x.size()-1] = 1 - std::accumulate(IO.x.begin(), IO.x.end()-1, 0.0);
304 // factor = rho_vap_new/IO.rhomolar_vap;
305 // dont_extrapolate = true; // So that we use the mole fractions we calculated here instead of the extrapolated values
306 // if (debug) std::cout << "[CRIT jump] new values: dv " << rho_vap_new << " dl " << IO.rhomolar_liq << " " << vec_to_string(IO.x, "%0.10Lg") << " " << vec_to_string(IO.y, "%0.10Lg") << std::endl;
307 // iter0 = iter - 1; // Back to linear interpolation again
308 // continue;
309 // }
310
311 dont_extrapolate = false;
312 if (iter < 5) {
313 continue;
314 }
315 if (IO.Nsteps > 10) {
316 factor = 1 + (factor - 1) / 10;
317 } else if (IO.Nsteps > 5) {
318 factor = 1 + (factor - 1) / 3;
319 } else if (IO.Nsteps <= 4) {
320 factor = 1 + (factor - 1) * 2;
321 }
322 // Min step is 1.01
323 factor = std::max(factor, static_cast<CoolPropDbl>(1.01));
324 // As we approach the critical point, control step size
325 if (std::abs(IO.rhomolar_liq / IO.rhomolar_vap - 1) < 4) {
326 // Max step is 1.1
327 factor = std::min(factor, static_cast<CoolPropDbl>(1.1));
328 }
329
330 // Stop if the pressure is below the starting pressure
331 // or if the composition of one of the phases becomes almost pure
332 CoolPropDbl max_fraction = *std::max_element(IO.x.begin(), IO.x.end());
333 if (iter > 4 && (IO.p < env.p[0] || std::abs(1.0 - max_fraction) < 1e-9)) {
334 env.built = true;
335 if (debug) {
336 std::cout << format("envelope built.\n");
337 std::cout << format("closest fraction to 1.0: distance %g\n", 1 - max_fraction);
338 }
339
340 // Now we refine the phase envelope to add some points in places that are still pretty rough
341 refine(HEOS, level);
342
343 return;
344 }
345
346 // Reset the failure counter
347 failure_count = 0;
348 }
349 }
350}
351
352void PhaseEnvelopeRoutines::refine(HelmholtzEOSMixtureBackend& HEOS, const std::string& level) {
353 bool debug = (get_debug_level() > 0 || false);
355 SaturationSolvers::newton_raphson_saturation NR;
356 SaturationSolvers::newton_raphson_saturation_options IO;
357 IO.imposed_variable = SaturationSolvers::newton_raphson_saturation_options::RHOV_IMPOSED;
358 IO.bubble_point = false;
359 IO.y = HEOS.get_mole_fractions();
360
361 double acceptable_pdiff = 0.5;
362 double acceptable_rhodiff = 0.25;
363 int N = 5; // Number of steps of refining
364 if (level == "veryfine") {
365 acceptable_pdiff = 0.1;
366 acceptable_rhodiff = 0.1;
367 }
368 if (level == "none") {
369 return;
370 }
371 std::size_t i = 0;
372 do {
373
374 // Don't do anything if change in density and pressure is small enough
375 if ((std::abs(env.rhomolar_vap[i] / env.rhomolar_vap[i + 1] - 1) < acceptable_rhodiff)
376 && (std::abs(env.p[i] / env.p[i + 1] - 1) < acceptable_pdiff)) {
377 i++;
378 continue;
379 }
380
381 // Ok, now we are going to do some more refining in this step
382
383 // Vapor densities for this step, vapor density monotonically increasing
384 const double rhomolar_vap_start = env.rhomolar_vap[i], rhomolar_vap_end = env.rhomolar_vap[i + 1];
385
386 double factor = pow(rhomolar_vap_end / rhomolar_vap_start, 1.0 / N);
387
388 int failure_count = 0;
389 for (double rhomolar_vap = rhomolar_vap_start * factor; rhomolar_vap < rhomolar_vap_end; rhomolar_vap *= factor) {
390 IO.rhomolar_vap = rhomolar_vap;
391 IO.x.resize(IO.y.size());
392 if (i < env.T.size() - 3) {
393 IO.T = CubicInterp(env.rhomolar_vap, env.T, i, i + 1, i + 2, i + 3, IO.rhomolar_vap);
394 IO.rhomolar_liq = CubicInterp(env.rhomolar_vap, env.rhomolar_liq, i, i + 1, i + 2, i + 3, IO.rhomolar_vap);
395 for (std::size_t j = 0; j < IO.x.size() - 1; ++j) { // First N-1 elements
396 IO.x[j] = CubicInterp(env.rhomolar_vap, env.x[j], i, i + 1, i + 2, i + 3, IO.rhomolar_vap);
397 }
398 } else {
399 IO.T = CubicInterp(env.rhomolar_vap, env.T, i, i - 1, i - 2, i - 3, IO.rhomolar_vap);
400 IO.rhomolar_liq = CubicInterp(env.rhomolar_vap, env.rhomolar_liq, i, i - 1, i - 2, i - 3, IO.rhomolar_vap);
401 for (std::size_t j = 0; j < IO.x.size() - 1; ++j) { // First N-1 elements
402 IO.x[j] = CubicInterp(env.rhomolar_vap, env.x[j], i, i - 1, i - 2, i - 3, IO.rhomolar_vap);
403 }
404 }
405 IO.x[IO.x.size() - 1] = 1 - std::accumulate(IO.x.begin(), IO.x.end() - 1, 0.0);
406 try {
407 NR.call(HEOS, IO.y, IO.x, IO);
408 if (!ValidNumber(IO.rhomolar_liq) || !ValidNumber(IO.p)) {
409 throw ValueError("invalid numbers");
410 }
411 env.insert_variables(IO.T, IO.p, IO.rhomolar_liq, IO.rhomolar_vap, IO.hmolar_liq, IO.hmolar_vap, IO.smolar_liq, IO.smolar_vap, IO.x,
412 IO.y, i + 1);
413 if (debug) {
414 std::cout << "dv " << IO.rhomolar_vap << " dl " << IO.rhomolar_liq << " T " << IO.T << " p " << IO.p << " hl " << IO.hmolar_liq
415 << " hv " << IO.hmolar_vap << " sl " << IO.smolar_liq << " sv " << IO.smolar_vap << " x "
416 << vec_to_string(IO.x, "%0.10Lg") << " Ns " << IO.Nsteps << std::endl;
417 }
418 } catch (...) {
419 failure_count++;
420 continue;
421 }
422 i++;
423 }
424 // If we had a failure, we don't want to get stuck on this value of i,
425 // so we bump up one and keep moving
426 if (failure_count > 0) {
427 i++;
428 }
429 } while (i < env.T.size() - 1);
430}
431double PhaseEnvelopeRoutines::evaluate(const PhaseEnvelopeData& env, parameters output, parameters iInput1, double value1, std::size_t& i) {
432 int _i = static_cast<int>(i);
433 std::vector<double> const *x, *y;
434
435 switch (output) {
436 case iT:
437 y = &(env.T);
438 break;
439 case iP:
440 y = &(env.p);
441 break;
442 case iDmolar:
443 y = &(env.rhomolar_vap);
444 break;
445 case iHmolar:
446 y = &(env.hmolar_vap);
447 break;
448 case iSmolar:
449 y = &(env.smolar_vap);
450 break;
451 case iCpmolar:
452 y = &(env.cpmolar_vap);
453 break;
454 case iCvmolar:
455 y = &(env.cvmolar_vap);
456 break;
457 case iviscosity:
458 y = &(env.viscosity_vap);
459 break;
460 case iconductivity:
461 y = &(env.conductivity_vap);
462 break;
463 case ispeed_sound:
464 y = &(env.speed_sound_vap);
465 break;
466 default:
467 throw ValueError("Pointer to vector y is unset in is_inside");
468 }
469
470 double inval = value1;
471 switch (iInput1) {
472 case iT:
473 x = &(env.T);
474 break;
475 case iP:
476 x = &(env.lnp);
477 inval = log(value1);
478 break;
479 case iDmolar:
480 x = &(env.rhomolar_vap);
481 break;
482 case iHmolar:
483 x = &(env.hmolar_vap);
484 break;
485 case iSmolar:
486 x = &(env.smolar_vap);
487 break;
488 default:
489 throw ValueError("Pointer to vector x is unset in is_inside");
490 }
491 if (_i + 2 >= static_cast<int>(y->size())) {
492 _i--;
493 }
494 if (_i + 1 >= static_cast<int>(y->size())) {
495 _i--;
496 }
497 if (_i - 1 < 0) {
498 _i++;
499 }
500
501 double outval = CubicInterp(*x, *y, _i - 1, _i, _i + 1, _i + 2, inval);
502 i = static_cast<std::size_t>(_i);
503 return outval;
504}
506 // No finalization for pure or pseudo-pure fluids
507 if (HEOS.get_mole_fractions_ref().size() == 1) {
508 return;
509 }
510
511 enum maxima_points
512 {
513 PMAX_SAT = 0,
514 TMAX_SAT = 1
515 };
516 std::size_t imax; // Index of the maximal temperature or pressure
517
519
520 // Find the index of the point with the highest temperature
521 std::size_t iTmax = std::distance(env.T.begin(), std::max_element(env.T.begin(), env.T.end()));
522
523 // Find the index of the point with the highest pressure
524 std::size_t ipmax = std::distance(env.p.begin(), std::max_element(env.p.begin(), env.p.end()));
525
526 // Determine if the phase envelope corresponds to a Type I mixture
527 // For now we consider a mixture to be Type I if the pressure at the
528 // end of the envelope is lower than max pressure pressure
529 env.TypeI = env.p[env.p.size() - 1] < env.p[ipmax];
530
531 // Approximate solutions for the maxima of the phase envelope
532 // See method in Gernert. We use our spline class to find the coefficients
533 if (env.TypeI) {
534 for (int imaxima = 0; imaxima <= 1; ++imaxima) {
535 maxima_points maxima;
536 if (imaxima == PMAX_SAT) {
537 maxima = PMAX_SAT;
538 } else if (imaxima == TMAX_SAT) {
539 maxima = TMAX_SAT;
540 } else {
541 throw ValueError("I don't understand your maxima index");
542 }
543
544 // Spline using the points around it
545 SplineClass spline;
546 if (maxima == TMAX_SAT) {
547 imax = iTmax;
548 if (iTmax > env.T.size() - 3) {
549 iTmax -= 2;
550 }
551 spline.add_4value_constraints(env.rhomolar_vap[iTmax - 1], env.rhomolar_vap[iTmax], env.rhomolar_vap[iTmax + 1],
552 env.rhomolar_vap[iTmax + 2], env.T[iTmax - 1], env.T[iTmax], env.T[iTmax + 1], env.T[iTmax + 2]);
553 } else {
554 imax = ipmax;
555 if (ipmax > env.p.size() - 3) {
556 ipmax -= 2;
557 }
558 spline.add_4value_constraints(env.rhomolar_vap[ipmax - 1], env.rhomolar_vap[ipmax], env.rhomolar_vap[ipmax + 1],
559 env.rhomolar_vap[ipmax + 2], env.p[ipmax - 1], env.p[ipmax], env.p[ipmax + 1], env.p[ipmax + 2]);
560 }
561 spline.build(); // y = a*rho^3 + b*rho^2 + c*rho + d
562
563 // Take derivative
564 // dy/drho = 3*a*rho^2 + 2*b*rho + c
565 // Solve quadratic for derivative to find rho
566 int Nsoln = 0;
567 double rho0 = _HUGE, rho1 = _HUGE, rho2 = _HUGE;
568 solve_cubic(0, 3 * spline.a, 2 * spline.b, spline.c, Nsoln, rho0, rho1, rho2);
569
570 SaturationSolvers::newton_raphson_saturation_options IO;
571 IO.rhomolar_vap = _HUGE;
572 // Find the correct solution
573 if (Nsoln == 1) {
574 IO.rhomolar_vap = rho0;
575 } else if (Nsoln == 2) {
576 if (is_in_closed_range(env.rhomolar_vap[imax - 1], env.rhomolar_vap[imax + 1], rho0)) {
577 IO.rhomolar_vap = rho0;
578 }
579 if (is_in_closed_range(env.rhomolar_vap[imax - 1], env.rhomolar_vap[imax + 1], rho1)) {
580 IO.rhomolar_vap = rho1;
581 }
582 } else {
583 throw ValueError("More than 2 solutions found");
584 }
585
586 class solver_resid : public FuncWrapper1D
587 {
588 public:
589 std::size_t imax;
590 maxima_points maxima;
592 SaturationSolvers::newton_raphson_saturation NR;
593 SaturationSolvers::newton_raphson_saturation_options IO;
594 solver_resid(HelmholtzEOSMixtureBackend& HEOS, std::size_t imax, maxima_points maxima) {
595 this->HEOS = &HEOS, this->imax = imax;
596 this->maxima = maxima;
597 };
598 double call(double rhomolar_vap) {
599 PhaseEnvelopeData& env = HEOS->PhaseEnvelope;
600 IO.imposed_variable = SaturationSolvers::newton_raphson_saturation_options::RHOV_IMPOSED;
601 IO.bubble_point = false;
602 IO.rhomolar_vap = rhomolar_vap;
603 IO.y = HEOS->get_mole_fractions();
604 IO.x = IO.y; // Just to give it good size
605 if (imax >= env.T.size() - 2) {
606 imax -= 2;
607 }
608 IO.T = CubicInterp(env.rhomolar_vap, env.T, imax - 1, imax, imax + 1, imax + 2, IO.rhomolar_vap);
609 IO.rhomolar_liq = CubicInterp(env.rhomolar_vap, env.rhomolar_liq, imax - 1, imax, imax + 1, imax + 2, IO.rhomolar_vap);
610 for (std::size_t i = 0; i < IO.x.size() - 1; ++i) // First N-1 elements
611 {
612 IO.x[i] = CubicInterp(env.rhomolar_vap, env.x[i], imax - 1, imax, imax + 1, imax + 2, IO.rhomolar_vap);
613 }
614 IO.x[IO.x.size() - 1] = 1 - std::accumulate(IO.x.begin(), IO.x.end() - 1, 0.0);
615 NR.call(*HEOS, IO.y, IO.x, IO);
616 if (maxima == TMAX_SAT) {
617 return NR.dTsat_dPsat;
618 } else {
619 return NR.dPsat_dTsat;
620 }
621 };
622 };
623
624 solver_resid resid(HEOS, imax, maxima);
625 try {
626 double rho = Brent(resid, IO.rhomolar_vap * 0.95, IO.rhomolar_vap * 1.05, DBL_EPSILON, 1e-12, 100);
627
628 // If maxima point is greater than density at point from the phase envelope, increase index by 1 so that the
629 // insertion will happen *after* the point in the envelope since density is monotonically increasing.
630 if (rho > env.rhomolar_vap[imax]) {
631 imax++;
632 }
633
634 env.insert_variables(resid.IO.T, resid.IO.p, resid.IO.rhomolar_liq, resid.IO.rhomolar_vap, resid.IO.hmolar_liq, resid.IO.hmolar_vap,
635 resid.IO.smolar_liq, resid.IO.smolar_vap, resid.IO.x, resid.IO.y, imax);
636 } catch (...) {
637 // Don't do the insertion
638 }
639 }
640 }
641
642 // Find the index of the point with the highest temperature
643 env.iTsat_max = std::distance(env.T.begin(), std::max_element(env.T.begin(), env.T.end()));
644
645 // Find the index of the point with the highest pressure
646 env.ipsat_max = std::distance(env.p.begin(), std::max_element(env.p.begin(), env.p.end()));
647}
648
649std::vector<std::pair<std::size_t, std::size_t>> PhaseEnvelopeRoutines::find_intersections(const PhaseEnvelopeData& env, parameters iInput,
650 double value) {
651 std::vector<std::pair<std::size_t, std::size_t>> intersections;
652
653 for (std::size_t i = 0; i < env.p.size() - 1; ++i) {
654 bool matched = false;
655 switch (iInput) {
656 case iP:
657 if (is_in_closed_range(env.p[i], env.p[i + 1], value)) {
658 matched = true;
659 }
660 break;
661 case iT:
662 if (is_in_closed_range(env.T[i], env.T[i + 1], value)) {
663 matched = true;
664 }
665 break;
666 case iHmolar:
667 if (is_in_closed_range(env.hmolar_vap[i], env.hmolar_vap[i + 1], value)) {
668 matched = true;
669 }
670 break;
671 case iSmolar:
672 if (is_in_closed_range(env.smolar_vap[i], env.smolar_vap[i + 1], value)) {
673 matched = true;
674 }
675 break;
676 default:
677 throw ValueError(format("bad index to find_intersections"));
678 }
679
680 if (matched) {
681 intersections.push_back(std::pair<std::size_t, std::size_t>(i, i + 1));
682 }
683 }
684 return intersections;
685}
687 std::size_t& iclosest, SimpleState& closest_state) {
688 // Find the indices that bound the solution(s)
689 std::vector<std::pair<std::size_t, std::size_t>> intersections = find_intersections(env, iInput1, value1);
690
691 if (get_debug_level() > 5) {
692 std::cout << format("is_inside(%Lg,%Lg); iTsat_max=%d; ipsat_max=%d\n", value1, value2, env.iTsat_max, env.ipsat_max);
693 }
694 // Check whether input is above max value
695 if (iInput1 == iT && 0 < env.iTsat_max && env.iTsat_max < env.T.size() && value1 > env.T[env.iTsat_max]) {
696 return false;
697 }
698 if (iInput1 == iP && 0 < env.ipsat_max && env.ipsat_max < env.p.size() && value1 > env.p[env.ipsat_max]) {
699 return false;
700 }
701
702 // If number of intersections is 0, input is out of range, quit
703 if (intersections.size() == 0) {
704 throw ValueError(format("Input is out of range for primary value [%Lg], inputs were (%s,%Lg,%s,%Lg); no intersections found", value1,
705 get_parameter_information(iInput1, "short").c_str(), value1, get_parameter_information(iInput2, "short").c_str(),
706 value2));
707 }
708
709 // If number of intersections is 1, input will be determined based on the single intersection
710 // Need to know if values increase or decrease to the right of the intersection point
711 if (intersections.size() % 2 != 0) {
712 throw ValueError("Input is weird; odd number of intersections found");
713 }
714
715 // If number of intersections is even, might be a bound
716 if (intersections.size() % 2 == 0) {
717 if (intersections.size() != 2) {
718 throw ValueError("for now only even value accepted is 2");
719 }
720 std::vector<std::size_t> other_indices(4, 0);
721 std::vector<double> const* y;
722 std::vector<double> other_values(4, 0);
723 other_indices[0] = intersections[0].first;
724 other_indices[1] = intersections[0].second;
725 other_indices[2] = intersections[1].first;
726 other_indices[3] = intersections[1].second;
727
728 switch (iInput2) {
729 case iT:
730 y = &(env.T);
731 break;
732 case iP:
733 y = &(env.p);
734 break;
735 case iDmolar:
736 y = &(env.rhomolar_vap);
737 break;
738 case iHmolar:
739 y = &(env.hmolar_vap);
740 break;
741 case iSmolar:
742 y = &(env.smolar_vap);
743 break;
744 default:
745 throw ValueError("Pointer to vector y is unset in is_inside");
746 }
747
748 other_values[0] = (*y)[other_indices[0]];
749 other_values[1] = (*y)[other_indices[1]];
750 other_values[2] = (*y)[other_indices[2]];
751 other_values[3] = (*y)[other_indices[3]];
752
753 CoolPropDbl min_other = *(std::min_element(other_values.begin(), other_values.end()));
754 CoolPropDbl max_other = *(std::max_element(other_values.begin(), other_values.end()));
755
756 if (get_debug_level() > 5) {
757 std::cout << format("is_inside: min: %Lg max: %Lg val: %Lg\n", min_other, max_other, value2);
758 }
759
760 // If by using the outer bounds of the second variable, we are outside the range,
761 // then the value is definitely not inside the phase envelope and we don't need to
762 // do any more analysis.
763 if (!is_in_closed_range(min_other, max_other, value2)) {
764 std::vector<CoolPropDbl> d(4, 0);
765 d[0] = std::abs(other_values[0] - value2);
766 d[1] = std::abs(other_values[1] - value2);
767 d[2] = std::abs(other_values[2] - value2);
768 d[3] = std::abs(other_values[3] - value2);
769
770 // Index of minimum distance in the other_values vector
771 std::size_t idist = std::distance(d.begin(), std::min_element(d.begin(), d.end()));
772 // Index of closest point in the phase envelope
773 iclosest = other_indices[idist];
774
775 // Get the state for the point which is closest to the desired value - this
776 // can be used as a bounding value in the outer single-phase flash routine
777 // since you know (100%) that it is a good bound
778 closest_state.T = env.T[iclosest];
779 closest_state.p = env.p[iclosest];
780 closest_state.rhomolar = env.rhomolar_vap[iclosest];
781 closest_state.hmolar = env.hmolar_vap[iclosest];
782 closest_state.smolar = env.smolar_vap[iclosest];
783 closest_state.Q = env.Q[iclosest];
784
785 if (get_debug_level() > 5) {
786 std::cout << format("is_inside: it is not inside") << std::endl;
787 }
788 return false;
789 } else {
790 // Now we have to do a saturation flash call in order to determine whether or not we are inside the phase envelope or not
791
792 // First we can interpolate using the phase envelope to get good guesses for the necessary values
793 CoolPropDbl y1 = evaluate(env, iInput2, iInput1, value1, intersections[0].first);
794 CoolPropDbl y2 = evaluate(env, iInput2, iInput1, value1, intersections[1].first);
795 if (is_in_closed_range(y1, y2, value2)) {
796 if (std::abs(y1 - value2) < std::abs(y2 - value2)) {
797 iclosest = intersections[0].first;
798 } else {
799 iclosest = intersections[1].first;
800 }
801 // Get the state for the point which is closest to the desired value - this
802 // can be used as a bounding value in the outer single-phase flash routine
803 // since you know (100%) that it is a good bound
804 closest_state.T = env.T[iclosest];
805 closest_state.p = env.p[iclosest];
806 closest_state.rhomolar = env.rhomolar_vap[iclosest];
807 closest_state.hmolar = env.hmolar_vap[iclosest];
808 closest_state.smolar = env.smolar_vap[iclosest];
809 closest_state.Q = env.Q[iclosest];
810 return true;
811 } else {
812 return false;
813 }
814 }
815 } else {
816 throw ValueError("You have a funny number of intersections in is_inside");
817 }
818}
819
820} /* namespace CoolProp */
821
822#endif