CoolProp 7.1.0
An open-source fluid property and humid air property database
HelmholtzEOSMixtureBackend.cpp
Go to the documentation of this file.
1/*
2 * AbstractBackend.cpp
3 *
4 * Created on: 20 Dec 2013
5 * Author: jowr
6 */
7
8#include <memory>
9
10#if defined(_MSC_VER)
11# define _CRTDBG_MAP_ALLOC
12# ifndef _CRT_SECURE_NO_WARNINGS
13# define _CRT_SECURE_NO_WARNINGS
14# endif
15# include <crtdbg.h>
16# include <sys/stat.h>
17#else
18# include <sys/stat.h>
19#endif
20
21#include <string>
22//#include "CoolProp.h"
23
25#include "HelmholtzEOSBackend.h"
26#include "Fluids/FluidLibrary.h"
27#include "Solvers.h"
28#include "MatrixMath.h"
29#include "VLERoutines.h"
30#include "FlashRoutines.h"
31#include "TransportRoutines.h"
32#include "MixtureDerivatives.h"
34#include "ReducingFunctions.h"
35#include "MixtureParameters.h"
36#include "IdealCurves.h"
37#include "MixtureParameters.h"
38#include <stdlib.h>
39
40static int deriv_counter = 0;
41
42namespace CoolProp {
43
45{
46 public:
47 AbstractState* get_AbstractState(const std::vector<std::string>& fluid_names) {
48 if (fluid_names.size() == 1) {
49 return new HelmholtzEOSBackend(fluid_names[0]);
50 } else {
51 return new HelmholtzEOSMixtureBackend(fluid_names);
52 }
53 };
54};
55// This static initialization will cause the generator to register
57
61 N = 0;
63 // Reset the residual Helmholtz energy class
65}
66HelmholtzEOSMixtureBackend::HelmholtzEOSMixtureBackend(const std::vector<std::string>& component_names, bool generate_SatL_and_SatV) {
67 std::vector<CoolPropFluid> components(component_names.size());
68 for (unsigned int i = 0; i < components.size(); ++i) {
69 components[i] = get_library().get(component_names[i]);
70 }
71
72 // Reset the residual Helmholtz energy class
74
75 // Set the components and associated flags
76 set_components(components, generate_SatL_and_SatV);
77
78 // Set the phase to default unknown value
80}
81HelmholtzEOSMixtureBackend::HelmholtzEOSMixtureBackend(const std::vector<CoolPropFluid>& components, bool generate_SatL_and_SatV) {
82
83 // Reset the residual Helmholtz energy class
85
86 // Set the components and associated flags
87 set_components(components, generate_SatL_and_SatV);
88
89 // Set the phase to default unknown value
91}
92void HelmholtzEOSMixtureBackend::set_components(const std::vector<CoolPropFluid>& components, bool generate_SatL_and_SatV) {
93
94 // Copy the components
95 this->components = components;
96 this->N = components.size();
97
98 is_pure_or_pseudopure = (components.size() == 1);
100 mole_fractions = std::vector<CoolPropDbl>(1, 1);
101 std::vector<std::vector<double>> ones(1, std::vector<double>(1, 1));
102 Reducing = shared_ptr<ReducingFunction>(new GERG2008ReducingFunction(components, ones, ones, ones, ones));
105 } else {
106 // Set the mixture parameters - binary pair reducing functions, departure functions, F_ij, etc.
108 }
109
111
112 // Top-level class can hold copies of the base saturation classes,
113 // saturation classes cannot hold copies of the saturation classes
114 if (generate_SatL_and_SatV) {
115 SatL.reset(get_copy(false));
116 SatL->specify_phase(iphase_liquid);
117 linked_states.push_back(SatL);
118 SatL->clear();
119 SatV.reset(get_copy(false));
120 SatV->specify_phase(iphase_gas);
121 SatV->clear();
122 linked_states.push_back(SatV);
123 }
124}
125void HelmholtzEOSMixtureBackend::set_mole_fractions(const std::vector<CoolPropDbl>& mf) {
126 if (mf.size() != N) {
127 throw ValueError(format("size of mole fraction vector [%d] does not equal that of component vector [%d]", mf.size(), N));
128 }
129 // Copy values without reallocating memory
130 this->mole_fractions = mf; // Most effective copy
131 this->resize(N); // No reallocation of this->mole_fractions happens
133};
135 residual_helmholtz.reset(source->residual_helmholtz->copy_ptr());
136 if (source->Reducing) {
137 Reducing.reset(source->Reducing->copy());
138 }
139 // Recurse into linked states of the class
140 for (std::vector<shared_ptr<HelmholtzEOSMixtureBackend>>::iterator it = linked_states.begin(); it != linked_states.end(); ++it) {
141 it->get()->sync_linked_states(source);
142 }
143}
145 // Set up the class with these components
146 HelmholtzEOSMixtureBackend* ptr = new HelmholtzEOSMixtureBackend(components, generate_SatL_and_SatV);
147 // Recursively walk into linked states, setting the departure and reducing terms
148 // to be equal to the parent (this instance)
149 ptr->sync_linked_states(this);
150 return ptr;
151};
152void HelmholtzEOSMixtureBackend::set_mass_fractions(const std::vector<CoolPropDbl>& mass_fractions) {
153 if (mass_fractions.size() != N) {
154 throw ValueError(format("size of mass fraction vector [%d] does not equal that of component vector [%d]", mass_fractions.size(), N));
155 }
156 std::vector<CoolPropDbl> moles;
157 CoolPropDbl sum_moles = 0.0;
158 CoolPropDbl tmp = 0.0;
159 for (unsigned int i = 0; i < components.size(); ++i) {
160 tmp = mass_fractions[i] / components[i].molar_mass();
161 moles.push_back(tmp);
162 sum_moles += tmp;
163 }
164 std::vector<CoolPropDbl> mole_fractions;
165 for (std::vector<CoolPropDbl>::iterator it = moles.begin(); it != moles.end(); ++it) {
166 mole_fractions.push_back(*it / sum_moles);
167 }
168 this->set_mole_fractions(mole_fractions);
169};
171 this->mole_fractions.resize(N);
172 this->K.resize(N);
173 this->lnK.resize(N);
174 for (std::vector<shared_ptr<HelmholtzEOSMixtureBackend>>::iterator it = linked_states.begin(); it != linked_states.end(); ++it) {
175 it->get()->N = N;
176 it->get()->resize(N);
177 }
178}
180 if (p() > p_critical()) {
181 if (T() > T_critical()) {
183 } else {
185 }
186 } else {
187 if (T() > T_critical()) {
189 } else {
190 // Liquid or vapor
191 if (rhomolar() > rhomolar_critical()) {
193 } else {
195 }
196 }
197 }
198}
199std::string HelmholtzEOSMixtureBackend::fluid_param_string(const std::string& ParamName) {
201 if (!ParamName.compare("name")) {
202 return cpfluid.name;
203 } else if (!ParamName.compare("aliases")) {
204 return strjoin(cpfluid.aliases, get_config_string(LIST_STRING_DELIMITER));
205 } else if (!ParamName.compare("aliases_bar")) {
206 return strjoin(cpfluid.aliases, "|");
207 } else if (!ParamName.compare("CAS") || !ParamName.compare("CAS_number")) {
208 return cpfluid.CAS;
209 } else if (!ParamName.compare("formula")) {
210 return cpfluid.formula;
211 } else if (!ParamName.compare("ASHRAE34")) {
212 return cpfluid.environment.ASHRAE34;
213 } else if (!ParamName.compare("REFPROPName") || !ParamName.compare("REFPROP_name") || !ParamName.compare("REFPROPname")) {
214 return cpfluid.REFPROPname;
215 } else if (ParamName.find("BibTeX") == 0) // Starts with "BibTeX"
216 {
217 std::vector<std::string> parts = strsplit(ParamName, '-');
218 if (parts.size() != 2) {
219 throw ValueError(format("Unable to parse BibTeX string %s", ParamName.c_str()));
220 }
221 std::string key = parts[1];
222 if (!key.compare("EOS")) {
223 return cpfluid.EOS().BibTeX_EOS;
224 } else if (!key.compare("CP0")) {
225 return cpfluid.EOS().BibTeX_CP0;
226 } else if (!key.compare("VISCOSITY")) {
227 return cpfluid.transport.BibTeX_viscosity;
228 } else if (!key.compare("CONDUCTIVITY")) {
229 return cpfluid.transport.BibTeX_conductivity;
230 } else if (!key.compare("ECS_LENNARD_JONES")) {
231 throw NotImplementedError();
232 } else if (!key.compare("ECS_VISCOSITY_FITS")) {
233 throw NotImplementedError();
234 } else if (!key.compare("ECS_CONDUCTIVITY_FITS")) {
235 throw NotImplementedError();
236 } else if (!key.compare("SURFACE_TENSION")) {
237 return cpfluid.ancillaries.surface_tension.BibTeX;
238 } else if (!key.compare("MELTING_LINE")) {
239 return cpfluid.ancillaries.melting_line.BibTeX;
240 } else {
241 throw CoolProp::KeyError(format("Bad key to get_BibTeXKey [%s]", key.c_str()));
242 }
243 } else if (ParamName.find("pure") == 0) {
244 if (is_pure()) {
245 return "true";
246 } else {
247 return "false";
248 }
249 } else if (ParamName == "INCHI" || ParamName == "InChI" || ParamName == "INCHI_STRING") {
250 return cpfluid.InChI;
251 } else if (ParamName == "INCHI_Key" || ParamName == "InChIKey" || ParamName == "INCHIKEY") {
252 return cpfluid.InChIKey;
253 } else if (ParamName == "2DPNG_URL") {
254 return cpfluid.TwoDPNG_URL;
255 } else if (ParamName == "SMILES" || ParamName == "smiles") {
256 return cpfluid.smiles;
257 } else if (ParamName == "CHEMSPIDER_ID") {
258 return format("%d", cpfluid.ChemSpider_id);
259 } else if (ParamName == "JSON") {
260 return get_fluid_as_JSONstring(cpfluid.CAS);
261 } else {
262 throw ValueError(format("fluid parameter [%s] is invalid", ParamName.c_str()));
263 }
264}
265
266std::optional<EquationOfState::SuperAncillary_t>& HelmholtzEOSMixtureBackend::get_superanc_optional(){
267 if (!is_pure()){
268 throw CoolProp::ValueError("Only available for pure (and not pseudo-pure) fluids");
269 }
270 return components[0].EOS().get_superanc_optional();
271}
272
273double HelmholtzEOSMixtureBackend::get_fluid_parameter_double(const size_t i, const std::string& parameter){
274 if (i >= N) {
275 throw ValueError(format("Index i [%d] is out of bounds. Must be between 0 and %d.", i, N-1));
276 }
277 auto& optsuperanc = get_superanc_optional();
278 if (parameter.find("SUPERANC::") == 0){
279 auto& superanc = optsuperanc.value();
280 if (optsuperanc){
281 std::string key = parameter.substr(10);
282 if (key == "pmax"){
283 return superanc.get_pmax();
284 }
285 else if (key == "pmin"){
286 return superanc.get_pmin();
287 }
288 else if (key == "Tmin"){
289 return superanc.get_Tmin();
290 }
291 else if (key == "Tcrit_num"){
292 return superanc.get_Tcrit_num();
293 }
294 else {
295 throw ValueError(format("Superancillary parameter [%s] is invalid", key.c_str()));
296 }
297 }
298 else{
299 throw ValueError(format("Superancillary not available for this fluid"));
300 }
301 } else {
302 throw ValueError(format("fluid parameter [%s] is invalid", parameter.c_str()));
303 }
304}
305
306
307void HelmholtzEOSMixtureBackend::apply_simple_mixing_rule(std::size_t i, std::size_t j, const std::string& model) {
308 // bound-check indices
309 if (i < 0 || i >= N) {
310 if (j < 0 || j >= N) {
311 throw ValueError(format("Both indices i [%d] and j [%d] are out of bounds. Must be between 0 and %d.", i, j, N-1));
312 } else {
313 throw ValueError(format("Index i [%d] is out of bounds. Must be between 0 and %d.", i, N-1));
314 }
315 } else if (j < 0 || j >= N) {
316 throw ValueError(format("Index j [%d] is out of bounds. Must be between 0 and %d.", j, N-1));
317 }
318 if (model == "linear") {
320 double gammaT = 0.5 * (Tc1 + Tc2) / sqrt(Tc1 * Tc2);
322 double gammaV = 4.0 * (1 / rhoc1 + 1 / rhoc2) / pow(pow(rhoc1, -1.0 / 3.0) + pow(rhoc2, -1.0 / 3.0), 3);
323 set_binary_interaction_double(i, j, "betaT", 1.0);
324 set_binary_interaction_double(i, j, "gammaT", gammaT);
325 set_binary_interaction_double(i, j, "betaV", 1.0);
326 set_binary_interaction_double(i, j, "gammaV", gammaV);
327 } else if (model == "Lorentz-Berthelot") {
328 set_binary_interaction_double(i, j, "betaT", 1.0);
329 set_binary_interaction_double(i, j, "gammaT", 1.0);
330 set_binary_interaction_double(i, j, "betaV", 1.0);
331 set_binary_interaction_double(i, j, "gammaV", 1.0);
332 } else {
333 throw ValueError(format("mixing rule [%s] is not understood", model.c_str()));
334 }
335}
337void HelmholtzEOSMixtureBackend::set_binary_interaction_double(const std::size_t i, const std::size_t j, const std::string& parameter,
338 const double value) {
339 // bound-check indices
340 if (i < 0 || i >= N) {
341 if (j < 0 || j >= N) {
342 throw ValueError(format("Both indices i [%d] and j [%d] are out of bounds. Must be between 0 and %d.", i, j, N-1));
343 } else {
344 throw ValueError(format("Index i [%d] is out of bounds. Must be between 0 and %d.", i, N-1));
345 }
346 } else if (j < 0 || j >= N) {
347 throw ValueError(format("Index j [%d] is out of bounds. Must be between 0 and %d.", j, N-1));
348 }
349 if (parameter == "Fij") {
350 residual_helmholtz->Excess.F[i][j] = value;
351 residual_helmholtz->Excess.F[j][i] = value;
352 } else {
353 Reducing->set_binary_interaction_double(i, j, parameter, value);
354 }
356 for (std::vector<shared_ptr<HelmholtzEOSMixtureBackend>>::iterator it = linked_states.begin(); it != linked_states.end(); ++it) {
357 it->get()->set_binary_interaction_double(i, j, parameter, value);
358 }
359};
361double HelmholtzEOSMixtureBackend::get_binary_interaction_double(const std::size_t i, const std::size_t j, const std::string& parameter) {
362 // bound-check indices
363 if (i < 0 || i >= N) {
364 if (j < 0 || j >= N) {
365 throw ValueError(format("Both indices i [%d] and j [%d] are out of bounds. Must be between 0 and %d.", i, j, N-1));
366 } else {
367 throw ValueError(format("Index i [%d] is out of bounds. Must be between 0 and %d.", i, N-1));
368 }
369 } else if (j < 0 || j >= N) {
370 throw ValueError(format("Index j [%d] is out of bounds. Must be between 0 and %d.", j, N-1));
371 }
372 if (parameter == "Fij") {
373 return residual_helmholtz->Excess.F[i][j];
374 } else {
375 return Reducing->get_binary_interaction_double(i, j, parameter);
376 }
377};
379//std::string HelmholtzEOSMixtureBackend::get_binary_interaction_string(const std::string &CAS1, const std::string &CAS2, const std::string &parameter){
380// return CoolProp::get_mixture_binary_pair_data(CAS1, CAS2, parameter);
381//}
383void HelmholtzEOSMixtureBackend::set_binary_interaction_string(const std::size_t i, const std::size_t j, const std::string& parameter,
384 const std::string& value) {
385 // bound-check indices
386 if (i < 0 || i >= N) {
387 if (j < 0 || j >= N) {
388 throw ValueError(format("Both indices i [%d] and j [%d] are out of bounds. Must be between 0 and %d.", i, j, N-1));
389 } else {
390 throw ValueError(format("Index i [%d] is out of bounds. Must be between 0 and %d.", i, N-1));
391 }
392 } else if (j < 0 || j >= N) {
393 throw ValueError(format("Index j [%d] is out of bounds. Must be between 0 and %d.", j, N-1));
394 }
395 if (parameter == "function") {
396 residual_helmholtz->Excess.DepartureFunctionMatrix[i][j].reset(get_departure_function(value));
397 residual_helmholtz->Excess.DepartureFunctionMatrix[j][i].reset(get_departure_function(value));
398 } else {
399 throw ValueError(format("Cannot process this string parameter [%s] in set_binary_interaction_string", parameter.c_str()));
400 }
402 for (std::vector<shared_ptr<HelmholtzEOSMixtureBackend>>::iterator it = linked_states.begin(); it != linked_states.end(); ++it) {
403 it->get()->set_binary_interaction_string(i, j, parameter, value);
404 }
405};
406
407void HelmholtzEOSMixtureBackend::calc_change_EOS(const std::size_t i, const std::string& EOS_name) {
408
409 if (i < components.size()) {
410 CoolPropFluid& fluid = components[i];
411 EquationOfState& EOS = fluid.EOSVector[0];
412
413 if (EOS_name == "SRK" || EOS_name == "Peng-Robinson") {
414
415 // Get the parameters for the cubic EOS
416 CoolPropDbl Tc = EOS.reduce.T;
417 CoolPropDbl pc = EOS.reduce.p;
418 CoolPropDbl rhomolarc = EOS.reduce.rhomolar;
419 CoolPropDbl acentric = EOS.acentric;
420 CoolPropDbl R = 8.3144598;
421
422 // Remove the residual part
423 EOS.alphar.empty_the_EOS();
424 // Set the contribution
425 shared_ptr<AbstractCubic> ac;
426 if (EOS_name == "SRK") {
427 ac.reset(new SRK(Tc, pc, acentric, R));
428 } else {
429 ac.reset(new PengRobinson(Tc, pc, acentric, R));
430 }
431 ac->set_Tr(Tc);
432 ac->set_rhor(rhomolarc);
434 } else if (EOS_name == "XiangDeiters") {
435
436 // Get the parameters for the EOS
437 CoolPropDbl Tc = EOS.reduce.T;
438 CoolPropDbl pc = EOS.reduce.p;
439 CoolPropDbl rhomolarc = EOS.reduce.rhomolar;
440 CoolPropDbl acentric = EOS.acentric;
441 CoolPropDbl R = 8.3144598;
442
443 // Remove the residual part
444 EOS.alphar.empty_the_EOS();
445 // Set the Xiang & Deiters contribution
446 EOS.alphar.XiangDeiters = ResidualHelmholtzXiangDeiters(Tc, pc, rhomolarc, acentric, R);
447 }
448 } else {
449 throw ValueError(format("Index [%d] is invalid", i));
450 }
451 // Now do the same thing to the saturated liquid and vapor instances if possible
452 if (this->SatL) SatL->change_EOS(i, EOS_name);
453 if (this->SatV) SatV->change_EOS(i, EOS_name);
454}
456 // Clear the phase envelope data
458 // Build the phase envelope
459 PhaseEnvelopeRoutines::build(*this, type);
460 // Finalize the phase envelope
462};
464 // Build the matrix of binary-pair reducing functions
466}
468 CoolPropFluid& component = components[0];
469 EquationOfState& EOS = component.EOSVector[0];
470
471 // Clear the state class
472 clear();
473
474 // Calculate the new enthalpy and entropy values
476 EOS.hs_anchor.hmolar = hmolar();
477 EOS.hs_anchor.smolar = smolar();
478
479 // Calculate the new enthalpy and entropy values at the reducing state
481 EOS.reduce.hmolar = hmolar();
482 EOS.reduce.smolar = smolar();
483
484 // Clear again just to be sure
485 clear();
486}
489 if (!state.compare("hs_anchor")) {
490 return components[0].EOS().hs_anchor;
491 } else if (!state.compare("max_sat_T")) {
492 return components[0].EOS().max_sat_T;
493 } else if (!state.compare("max_sat_p")) {
494 return components[0].EOS().max_sat_p;
495 } else if (!state.compare("reducing")) {
496 return components[0].EOS().reduce;
497 } else if (!state.compare("critical")) {
498 return components[0].crit;
499 } else if (!state.compare("triple_liquid")) {
500 return components[0].triple_liquid;
501 } else if (!state.compare("triple_vapor")) {
502 return components[0].triple_vapor;
503 } else {
504 throw ValueError(format("This state [%s] is invalid to calc_state", state.c_str()));
505 }
506 } else {
507 if (!state.compare("critical")) {
508 return _critical;
509 } else {
510 throw ValueError(format("calc_state not supported for mixtures"));
511 }
512 }
513};
516 return components[0].EOS().acentric;
517 } else {
518 throw ValueError("acentric factor cannot be calculated for mixtures");
519 }
520}
523 return components[0].gas_constant();
524 } else {
525 if (get_config_bool(NORMALIZE_GAS_CONSTANTS)) {
526 return get_config_double(R_U_CODATA);
527 } else {
528 // mass fraction weighted average of the components
529 double summer = 0;
530 for (unsigned int i = 0; i < components.size(); ++i) {
531 summer += mole_fractions[i] * components[i].gas_constant();
532 }
533 return summer;
534 }
535 }
536}
538 double summer = 0;
539 for (unsigned int i = 0; i < components.size(); ++i) {
540 summer += mole_fractions[i] * components[i].molar_mass();
541 }
542 return summer;
543}
546 if (param == iP && given == iT) {
547 // p = f(T), direct evaluation
548 switch (Q) {
549 case 0:
550 return components[0].ancillaries.pL.evaluate(value);
551 case 1:
552 return components[0].ancillaries.pV.evaluate(value);
553 }
554 } else if (param == iT && given == iP) {
555 // T = f(p), inverse evaluation
556 switch (Q) {
557 case 0:
558 return components[0].ancillaries.pL.invert(value);
559 case 1:
560 return components[0].ancillaries.pV.invert(value);
561 }
562 } else if (param == iDmolar && given == iT) {
563 // rho = f(T), inverse evaluation
564 switch (Q) {
565 case 0:
566 return components[0].ancillaries.rhoL.evaluate(value);
567 case 1:
568 return components[0].ancillaries.rhoV.evaluate(value);
569 }
570 } else if (param == iT && given == iDmolar) {
571 // T = f(rho), inverse evaluation
572 switch (Q) {
573 case 0:
574 return components[0].ancillaries.rhoL.invert(value);
575 case 1:
576 return components[0].ancillaries.rhoV.invert(value);
577 }
578 } else if (param == isurface_tension && given == iT) {
579 return components[0].ancillaries.surface_tension.evaluate(value);
580 } else {
581 throw ValueError(format("calc of %s given %s is invalid in calc_saturation_ancillary", get_parameter_information(param, "short").c_str(),
582 get_parameter_information(given, "short").c_str()));
583 }
584
585 throw ValueError(format("Q [%d] is invalid in calc_saturation_ancillary", Q));
586 } else {
587 throw NotImplementedError(format("calc_saturation_ancillary not implemented for mixtures"));
588 }
589}
590
593 return components[0].ancillaries.melting_line.evaluate(param, given, value);
594 } else {
595 throw NotImplementedError(format("calc_melting_line not implemented for mixtures"));
596 }
597}
600 if ((_phase == iphase_twophase) || (_phase == iphase_critical_point)) { // if within the two phase region or at critical point
601 return components[0].ancillaries.surface_tension.evaluate(T()); // calculate surface tension and return
602 } else { // else state point not in the two phase region
603 throw ValueError(format("surface tension is only defined within the two-phase region; Try PQ or QT inputs")); // throw error
604 }
605 } else {
606 throw NotImplementedError(format("surface tension not implemented for mixtures"));
607 }
608}
611 CoolPropDbl eta_dilute;
612 switch (components[0].transport.viscosity_dilute.type) {
615 break;
618 break;
621 break;
624 break;
627 break;
630 break;
633 break;
636 break;
637 default:
638 throw ValueError(
639 format("dilute viscosity type [%d] is invalid for fluid %s", components[0].transport.viscosity_dilute.type, name().c_str()));
640 }
641 return eta_dilute;
642 } else {
643 throw NotImplementedError(format("dilute viscosity not implemented for mixtures"));
644 }
645}
647 CoolPropDbl eta_dilute = calc_viscosity_dilute(), initial_density = 0, residual = 0;
648 return calc_viscosity_background(eta_dilute, initial_density, residual);
649}
651
652 switch (components[0].transport.viscosity_initial.type) {
655 CoolPropDbl rho = rhomolar();
656 initial_density = eta_dilute * B_eta_initial * rho; //TODO: Check units once AMTG
657 break;
658 }
661 break;
662 }
664 break;
665 }
666 }
667
668 // Higher order terms
669 switch (components[0].transport.viscosity_higher_order.type) {
672 break;
674 residual = TransportRoutines::viscosity_higher_order_friction_theory(*this);
675 break;
678 break;
681 break;
684 break;
687 break;
690 break;
693 break;
696 break;
697 default:
698 throw ValueError(
699 format("higher order viscosity type [%d] is invalid for fluid %s", components[0].transport.viscosity_dilute.type, name().c_str()));
700 }
701
702 return initial_density + residual;
703}
704
707 CoolPropDbl dilute = 0, initial_density = 0, residual = 0, critical = 0;
708 calc_viscosity_contributions(dilute, initial_density, residual, critical);
709 return dilute + initial_density + residual + critical;
710 } else {
711 set_warning_string("Mixture model for viscosity is highly approximate");
712 CoolPropDbl summer = 0;
713 for (std::size_t i = 0; i < mole_fractions.size(); ++i) {
714 shared_ptr<HelmholtzEOSBackend> HEOS(new HelmholtzEOSBackend(components[i]));
715 HEOS->update(DmolarT_INPUTS, _rhomolar, _T);
716 summer += mole_fractions[i] * log(HEOS->viscosity());
717 }
718 return exp(summer);
719 }
720}
722 CoolPropDbl& critical) {
724 // Reset the variables
725 dilute = 0;
726 initial_density = 0;
727 residual = 0;
728 critical = 0;
729
730 // Get a reference for code cleanness
731 CoolPropFluid& component = components[0];
732
733 if (!component.transport.viscosity_model_provided) {
734 throw ValueError(format("Viscosity model is not available for this fluid"));
735 }
736
737 // Check if using ECS
738 if (component.transport.viscosity_using_ECS) {
739 // Get reference fluid name
740 std::string fluid_name = component.transport.viscosity_ecs.reference_fluid;
741 std::vector<std::string> names(1, fluid_name);
742 // Get a managed pointer to the reference fluid for ECS
743 shared_ptr<HelmholtzEOSMixtureBackend> ref_fluid(new HelmholtzEOSMixtureBackend(names));
744 // Get the viscosity using ECS and stick in the critical value
745 critical = TransportRoutines::viscosity_ECS(*this, *ref_fluid);
746 return;
747 }
748
749 // Check if using Chung model
750 if (component.transport.viscosity_using_Chung) {
751 // Get the viscosity using ECS and stick in the critical value
752 critical = TransportRoutines::viscosity_Chung(*this);
753 return;
754 }
755
756 // Check if using rho*sr model
757 if (component.transport.viscosity_using_rhosr) {
758 // Get the viscosity using rho*sr model and stick in the critical value
759 critical = TransportRoutines::viscosity_rhosr(*this);
760 return;
761 }
762
764 // Evaluate hardcoded model and stick in the critical value
765 switch (component.transport.hardcoded_viscosity) {
768 break;
771 break;
774 break;
777 break;
780 break;
783 break;
786 break;
789 break;
790 default:
791 throw ValueError(
792 format("hardcoded viscosity type [%d] is invalid for fluid %s", component.transport.hardcoded_viscosity, name().c_str()));
793 }
794 return;
795 }
796
797 // -------------------------
798 // Normal evaluation
799 // -------------------------
800
801 // Dilute part
802 dilute = calc_viscosity_dilute();
803
804 // Background viscosity given by the sum of the initial density dependence and higher order terms
805 calc_viscosity_background(dilute, initial_density, residual);
806
807 // Critical part (no fluids have critical enhancement for viscosity currently)
808 critical = 0;
809 } else {
810 throw ValueError("calc_viscosity_contributions invalid for mixtures");
811 }
812}
814 CoolPropDbl& critical) {
816 // Reset the variables
817 dilute = 0;
818 initial_density = 0;
819 residual = 0;
820 critical = 0;
821
822 // Get a reference for code cleanness
823 CoolPropFluid& component = components[0];
824
825 if (!component.transport.conductivity_model_provided) {
826 throw ValueError(format("Thermal conductivity model is not available for this fluid"));
827 }
828
829 // Check if using ECS
830 if (component.transport.conductivity_using_ECS) {
831 // Get reference fluid name
832 std::string fluid_name = component.transport.conductivity_ecs.reference_fluid;
833 std::vector<std::string> name(1, fluid_name);
834 // Get a managed pointer to the reference fluid for ECS
835 shared_ptr<HelmholtzEOSMixtureBackend> ref_fluid(new HelmholtzEOSMixtureBackend(name));
836 // Get the viscosity using ECS and store in initial_density (not normally used);
837 initial_density = TransportRoutines::conductivity_ECS(*this, *ref_fluid); // Warning: not actually initial_density
838 return;
839 }
840
842 // Evaluate hardcoded model and deposit in initial_density variable
843 // Warning: not actually initial_density
844 switch (component.transport.hardcoded_conductivity) {
846 initial_density = TransportRoutines::conductivity_hardcoded_water(*this);
847 break;
849 initial_density = TransportRoutines::conductivity_hardcoded_heavywater(*this);
850 break;
852 initial_density = TransportRoutines::conductivity_hardcoded_R23(*this);
853 break;
855 initial_density = TransportRoutines::conductivity_hardcoded_helium(*this);
856 break;
858 initial_density = TransportRoutines::conductivity_hardcoded_methane(*this);
859 break;
860 default:
861 throw ValueError(format("hardcoded conductivity type [%d] is invalid for fluid %s",
862 components[0].transport.hardcoded_conductivity, name().c_str()));
863 }
864 return;
865 }
866
867 // -------------------------
868 // Normal evaluation
869 // -------------------------
870
871 // Dilute part
872 switch (component.transport.conductivity_dilute.type) {
874 dilute = TransportRoutines::conductivity_dilute_ratio_polynomials(*this);
875 break;
877 dilute = TransportRoutines::conductivity_dilute_eta0_and_poly(*this);
878 break;
880 dilute = TransportRoutines::conductivity_dilute_hardcoded_CO2(*this);
881 break;
883 dilute = TransportRoutines::conductivity_dilute_hardcoded_CO2_HuberJPCRD2016(*this);
884 break;
886 dilute = TransportRoutines::conductivity_dilute_hardcoded_ethane(*this);
887 break;
889 dilute = 0.0;
890 break;
891 default:
892 throw ValueError(
893 format("dilute conductivity type [%d] is invalid for fluid %s", components[0].transport.conductivity_dilute.type, name().c_str()));
894 }
895
896 // Residual part
897 residual = calc_conductivity_background();
898
899 // Critical part
900 switch (component.transport.conductivity_critical.type) {
902 critical = TransportRoutines::conductivity_critical_simplified_Olchowy_Sengers(*this);
903 break;
905 critical = TransportRoutines::conductivity_critical_hardcoded_R123(*this);
906 break;
908 critical = TransportRoutines::conductivity_critical_hardcoded_ammonia(*this);
909 break;
911 critical = 0.0;
912 break;
914 critical = TransportRoutines::conductivity_critical_hardcoded_CO2_ScalabrinJPCRD2006(*this);
915 break;
916 default:
917 throw ValueError(
918 format("critical conductivity type [%d] is invalid for fluid %s", components[0].transport.viscosity_dilute.type, name().c_str()));
919 }
920 } else {
921 throw ValueError("calc_conductivity_contributions invalid for mixtures");
922 }
923};
924
926 // Residual part
927 CoolPropDbl lambda_residual = _HUGE;
928 switch (components[0].transport.conductivity_residual.type) {
930 lambda_residual = TransportRoutines::conductivity_residual_polynomial(*this);
931 break;
933 lambda_residual = TransportRoutines::conductivity_residual_polynomial_and_exponential(*this);
934 break;
935 default:
936 throw ValueError(
937 format("residual conductivity type [%d] is invalid for fluid %s", components[0].transport.conductivity_residual.type, name().c_str()));
938 }
939 return lambda_residual;
940}
943 CoolPropDbl dilute = 0, initial_density = 0, residual = 0, critical = 0;
944 calc_conductivity_contributions(dilute, initial_density, residual, critical);
945 return dilute + initial_density + residual + critical;
946 } else {
947 set_warning_string("Mixture model for conductivity is highly approximate");
948 CoolPropDbl summer = 0;
949 for (std::size_t i = 0; i < mole_fractions.size(); ++i) {
950 shared_ptr<HelmholtzEOSBackend> HEOS(new HelmholtzEOSBackend(components[i]));
951 HEOS->update(DmolarT_INPUTS, _rhomolar, _T);
952 summer += mole_fractions[i] * HEOS->conductivity();
953 }
954 return summer;
955 }
956}
957void HelmholtzEOSMixtureBackend::calc_conformal_state(const std::string& reference_fluid, CoolPropDbl& T, CoolPropDbl& rhomolar) {
958 shared_ptr<CoolProp::HelmholtzEOSMixtureBackend> REF(new CoolProp::HelmholtzEOSBackend(reference_fluid));
959
960 if (T < 0 && rhomolar < 0) {
961 // Collect some parameters
962 CoolPropDbl Tc = T_critical(), Tc0 = REF->T_critical(), rhocmolar = rhomolar_critical(), rhocmolar0 = REF->rhomolar_critical();
963
964 // Starting guess values for shape factors
965 CoolPropDbl theta = 1;
966 CoolPropDbl phi = 1;
967
968 // The equivalent substance reducing ratios
969 CoolPropDbl f = Tc / Tc0 * theta;
970 CoolPropDbl h = rhocmolar0 / rhocmolar * phi; // Must be the ratio of MOLAR densities!!
971
972 // Starting guesses for conformal state
973 T = this->T() / f;
974 rhomolar = this->rhomolar() * h;
975 }
976
977 TransportRoutines::conformal_state_solver(*this, *REF, T, rhomolar);
978}
980 double summer = 0;
981 for (unsigned int i = 0; i < components.size(); ++i) {
982 summer += mole_fractions[i] * components[i].EOS().Ttriple;
983 }
984 return summer;
985}
987 double summer = 0;
988 for (unsigned int i = 0; i < components.size(); ++i) {
989 summer += mole_fractions[i] * components[i].EOS().ptriple;
990 }
991 return summer;
992}
994 if (components.size() != 1) {
995 throw ValueError(format("calc_name is only valid for pure and pseudo-pure fluids, %d components", components.size()));
996 } else {
997 return components[0].name;
998 }
999}
1000
1002 if ((key == iDmolar) && _rhoLmolar) return _rhoLmolar;
1003 if (!SatL) throw ValueError("The saturated liquid state has not been set.");
1004 return SatL->keyed_output(key);
1005}
1007 if ((key == iDmolar) && _rhoVmolar) return _rhoVmolar;
1008 if (!SatV) throw ValueError("The saturated vapor state has not been set.");
1009 return SatV->keyed_output(key);
1010}
1011
1012void HelmholtzEOSMixtureBackend::calc_ideal_curve(const std::string& type, std::vector<double>& T, std::vector<double>& p) {
1013 if (type == "Joule-Thomson") {
1014 JouleThomsonCurveTracer JTCT(this, 1e5, 800);
1015 JTCT.trace(T, p);
1016 } else if (type == "Joule-Inversion") {
1017 JouleInversionCurveTracer JICT(this, 1e5, 800);
1018 JICT.trace(T, p);
1019 } else if (type == "Ideal") {
1020 IdealCurveTracer ICT(this, 1e5, 800);
1021 ICT.trace(T, p);
1022 } else if (type == "Boyle") {
1023 BoyleCurveTracer BCT(this, 1e5, 800);
1024 BCT.trace(T, p);
1025 } else {
1026 throw ValueError(format("Invalid ideal curve type: %s", type.c_str()));
1027 }
1028};
1029std::vector<std::string> HelmholtzEOSMixtureBackend::calc_fluid_names(void) {
1030 std::vector<std::string> out;
1031 for (std::size_t i = 0; i < components.size(); ++i) {
1032 out.push_back(components[i].name);
1033 }
1034 return out;
1035}
1037 if (components.size() != 1) {
1038 throw ValueError(format("For now, calc_ODP is only valid for pure and pseudo-pure fluids, %d components", components.size()));
1039 } else {
1040 CoolPropDbl v = components[0].environment.ODP;
1041 if (!ValidNumber(v) || v < 0) {
1042 throw ValueError(format("ODP value is not specified or invalid"));
1043 }
1044 return v;
1045 }
1046}
1048 if (components.size() != 1) {
1049 throw ValueError(format("For now, calc_GWP20 is only valid for pure and pseudo-pure fluids, %d components", components.size()));
1050 } else {
1051 CoolPropDbl v = components[0].environment.GWP20;
1052 if (!ValidNumber(v) || v < 0) {
1053 throw ValueError(format("GWP20 value is not specified or invalid"));
1054 }
1055 return v;
1056 }
1057}
1059 if (components.size() != 1) {
1060 throw ValueError(format("For now, calc_GWP100 is only valid for pure and pseudo-pure fluids, %d components", components.size()));
1061 } else {
1062 CoolPropDbl v = components[0].environment.GWP100;
1063 if (!ValidNumber(v) || v < 0) {
1064 throw ValueError(format("GWP100 value is not specified or invalid"));
1065 }
1066 return v;
1067 }
1068}
1070 if (components.size() != 1) {
1071 throw ValueError(format("For now, calc_GWP500 is only valid for pure and pseudo-pure fluids, %d components", components.size()));
1072 } else {
1073 CoolPropDbl v = components[0].environment.GWP500;
1074 if (!ValidNumber(v) || v < 0) {
1075 throw ValueError(format("GWP500 value is not specified or invalid"));
1076 }
1077 return v;
1078 }
1079}
1081 if (components.size() != 1) {
1082 std::vector<CriticalState> critpts = calc_all_critical_points();
1083 if (critpts.size() == 1) {
1084 //if (!critpts[0].stable){ throw ValueError(format("found one critical point but critical point is not stable")); }
1085 return critpts[0].T;
1086 } else {
1087 throw ValueError(format("critical point finding routine found %d critical points", critpts.size()));
1088 }
1089 } else {
1090 if (get_config_bool(ENABLE_SUPERANCILLARIES) && is_pure()){
1091 auto& optsuperanc = get_superanc_optional();
1092 if (optsuperanc){
1093 return optsuperanc.value().get_Tcrit_num(); // from the numerical critical point satisfying dp/drho|T = d2p/drho2|T = 0
1094 }
1095 }
1096 return components[0].crit.T;
1097 }
1098}
1100 if (components.size() != 1) {
1101 std::vector<CriticalState> critpts = calc_all_critical_points();
1102 if (critpts.size() == 1) {
1103 //if (!critpts[0].stable){ throw ValueError(format("found one critical point but critical point is not stable")); }
1104 return critpts[0].p;
1105 } else {
1106 throw ValueError(format("critical point finding routine found %d critical points", critpts.size()));
1107 }
1108 } else {
1109 if (get_config_bool(ENABLE_SUPERANCILLARIES) && is_pure()){
1110 auto& optsuperanc = get_superanc_optional();
1111 if (optsuperanc){
1112 return optsuperanc.value().get_pmax(); // from the numerical critical point satisfying dp/drho|T = d2p/drho2|T = 0
1113 }
1114 }
1115 return components[0].crit.p;
1116 }
1117}
1119 if (components.size() != 1) {
1120 std::vector<CriticalState> critpts = calc_all_critical_points();
1121 if (critpts.size() == 1) {
1122 //if (!critpts[0].stable){ throw ValueError(format("found one critical point but critical point is not stable")); }
1123 return critpts[0].rhomolar;
1124 } else {
1125 throw ValueError(format("critical point finding routine found %d critical points", critpts.size()));
1126 }
1127 } else {
1128 if (get_config_bool(ENABLE_SUPERANCILLARIES) && is_pure()){
1129 auto& optsuperanc = get_superanc_optional();
1130 if (optsuperanc){
1131 return optsuperanc.value().get_rhocrit_num(); // from the numerical critical point satisfying dp/drho|T = d2p/drho2|T = 0
1132 }
1133 }
1134 return components[0].crit.rhomolar;
1135 }
1136}
1139 if (components[0].EOS().pseudo_pure) {
1140 return components[0].EOS().max_sat_p.p;
1141 } else {
1142 return p_critical();
1143 }
1144 } else {
1145 throw ValueError("calc_pmax_sat not yet defined for mixtures");
1146 }
1147}
1150 if (components[0].EOS().pseudo_pure) {
1151 double Tmax_sat = components[0].EOS().max_sat_T.T;
1152 if (!ValidNumber(Tmax_sat)) {
1153 return T_critical();
1154 } else {
1155 return Tmax_sat;
1156 }
1157 } else {
1158 return T_critical();
1159 }
1160 } else {
1161 throw ValueError("calc_Tmax_sat not yet defined for mixtures");
1162 }
1163}
1164
1167 Tmin_satL = components[0].EOS().sat_min_liquid.T;
1168 Tmin_satV = components[0].EOS().sat_min_vapor.T;
1169 return;
1170 } else {
1171 throw ValueError("calc_Tmin_sat not yet defined for mixtures");
1172 }
1173}
1174
1177 pmin_satL = components[0].EOS().sat_min_liquid.p;
1178 pmin_satV = components[0].EOS().sat_min_vapor.p;
1179 return;
1180 } else {
1181 throw ValueError("calc_pmin_sat not yet defined for mixtures");
1182 }
1183}
1184
1185// Minimum allowed saturation temperature the maximum of the saturation temperatures of liquid and vapor
1186// For pure fluids, both values are the same, for pseudo-pure they are probably the same, for mixtures they are definitely not the same
1187
1189 double summer = 0;
1190 for (unsigned int i = 0; i < components.size(); ++i) {
1191 summer += mole_fractions[i] * components[i].EOS().limits.Tmax;
1192 }
1193 return summer;
1194}
1196 double summer = 0;
1197 for (unsigned int i = 0; i < components.size(); ++i) {
1198 summer += mole_fractions[i] * components[i].EOS().limits.Tmin;
1199 }
1200 return summer;
1201}
1203 double summer = 0;
1204 for (unsigned int i = 0; i < components.size(); ++i) {
1205 summer += mole_fractions[i] * components[i].EOS().limits.pmax;
1206 }
1207 return summer;
1208}
1209
1211 clear();
1212 _Q = Q;
1213 _T = T;
1214 if (!is_pure()){
1215 throw ValueError(format("Is not a pure fluid"));
1216 }
1217 if (!get_config_bool(ENABLE_SUPERANCILLARIES)){
1218 throw ValueError(format("Superancillaries are not enabled"));
1219 }
1220 auto& optsuperanc = get_superanc_optional();
1221 if (!optsuperanc){
1222 throw ValueError(format("Superancillaries not available for this fluid"));
1223 }
1224
1225 auto& superanc = optsuperanc.value();
1226 CoolPropDbl Tcrit_num = superanc.get_Tcrit_num();
1227 if (T > Tcrit_num){
1228 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));
1229 }
1230 auto rhoL = superanc.eval_sat(T, 'D', 0);
1231 auto rhoV = superanc.eval_sat(T, 'D', 1);
1232 auto p = superanc.eval_sat(T, 'P', 1);
1233 SatL->update_TDmolarP_unchecked(T, rhoL, p);
1234 SatV->update_TDmolarP_unchecked(T, rhoV, p);
1235 _p = p;
1236 _rhomolar = 1 / (Q / rhoV + (1 - Q) / rhoL);
1238
1239 post_update(false /*optional_checks*/);
1240}
1241
1242
1244
1245 clear();
1246
1248 _T = T;
1249 _p = p;
1250
1251 // Cleanup
1252 bool optional_checks = false;
1253 post_update(optional_checks);
1254}
1255
1256
1257
1259 // TODO: This is just a quick fix for #878 - should be done more systematically
1260 const CoolPropDbl rhomolar_min = 0;
1261 const CoolPropDbl T_min = 0;
1262
1263 if (rhomolar < rhomolar_min) {
1264 throw ValueError(format("The molar density of %f mol/m3 is below the minimum of %f mol/m3", rhomolar, rhomolar_min));
1265 }
1266
1267 if (T < T_min) {
1268 throw ValueError(format("The temperature of %f K is below the minimum of %f K", T, T_min));
1269 }
1270
1272 // Set up the state
1273 pre_update(pair, rhomolar, T);
1274
1276 _T = T;
1277 _p = calc_pressure();
1278
1279 // Cleanup
1280 bool optional_checks = false;
1281 post_update(optional_checks);
1282}
1283
1286 // Set up the state
1287 pre_update(pair, hmolar, Q);
1288
1289 _hmolar = hmolar;
1290 _Q = Q;
1291 FlashRoutines::HQ_flash(*this, Tguess);
1292
1293 // Cleanup
1294 post_update();
1295}
1297 this->_hmolar = HEOS.hmolar();
1298 this->_smolar = HEOS.smolar();
1299 this->_T = HEOS.T();
1300 this->_umolar = HEOS.umolar();
1301 this->_p = HEOS.p();
1302 this->_rhomolar = HEOS.rhomolar();
1303 this->_Q = HEOS.Q();
1304 this->_phase = HEOS.phase();
1305
1306 // Copy the derivatives as well
1307}
1310 // Set up the state
1311 pre_update(pair, p, T);
1312
1313 // Do the flash call to find rho = f(T,p)
1314 CoolPropDbl rhomolar = solver_rho_Tp(T, p, rhomolar_guess);
1315
1316 // Update the class with the new calculated density
1318
1319 // Skip the cleanup, already done in update_DmolarT_direct
1320}
1321
1323 // Clear the state
1324 clear();
1325
1326 if (is_pure_or_pseudopure == false && mole_fractions.size() == 0) {
1327 throw ValueError("Mole fractions must be set");
1328 }
1329
1330 // If the inputs are in mass units, convert them to molar units
1331 mass_to_molar_inputs(input_pair, value1, value2);
1332
1333 // Set the mole-fraction weighted gas constant for the mixture
1334 // (or the pure/pseudo-pure fluid) if it hasn't been set yet
1335 gas_constant();
1336
1337 // Calculate and cache the reducing state
1339}
1340
1341void HelmholtzEOSMixtureBackend::update(CoolProp::input_pairs input_pair, double value1, double value2) {
1342 if (get_debug_level() > 10) {
1343 std::cout << format("%s (%d): update called with (%d: (%s), %g, %g)", __FILE__, __LINE__, input_pair,
1344 get_input_pair_short_desc(input_pair).c_str(), value1, value2)
1345 << std::endl;
1346 }
1347
1348 CoolPropDbl ld_value1 = value1, ld_value2 = value2;
1349 pre_update(input_pair, ld_value1, ld_value2);
1350 value1 = ld_value1;
1351 value2 = ld_value2;
1352
1353 switch (input_pair) {
1354 case PT_INPUTS:
1355 _p = value1;
1356 _T = value2;
1358 break;
1359 case DmolarT_INPUTS:
1360 _rhomolar = value1;
1361 _T = value2;
1363 break;
1364 case SmolarT_INPUTS:
1365 _smolar = value1;
1366 _T = value2;
1368 break;
1369 //case HmolarT_INPUTS:
1370 // _hmolar = value1; _T = value2; FlashRoutines::DHSU_T_flash(*this, iHmolar); break;
1371 //case TUmolar_INPUTS:
1372 // _T = value1; _umolar = value2; FlashRoutines::DHSU_T_flash(*this, iUmolar); break;
1373 case DmolarP_INPUTS:
1374 _rhomolar = value1;
1375 _p = value2;
1377 break;
1379 _rhomolar = value1;
1380 _hmolar = value2;
1382 break;
1384 _rhomolar = value1;
1385 _smolar = value2;
1387 break;
1389 _rhomolar = value1;
1390 _umolar = value2;
1392 break;
1393 case HmolarP_INPUTS:
1394 _hmolar = value1;
1395 _p = value2;
1397 break;
1398 case PSmolar_INPUTS:
1399 _p = value1;
1400 _smolar = value2;
1402 break;
1403 case PUmolar_INPUTS:
1404 _p = value1;
1405 _umolar = value2;
1407 break;
1409 _hmolar = value1;
1410 _smolar = value2;
1412 break;
1413 case QT_INPUTS:
1414 _Q = value1;
1415 _T = value2;
1416 if ((_Q < 0) || (_Q > 1)) throw CoolProp::OutOfRangeError("Input vapor quality [Q] must be between 0 and 1");
1418 break;
1419 case PQ_INPUTS:
1420 _p = value1;
1421 _Q = value2;
1422 if ((_Q < 0) || (_Q > 1)) throw CoolProp::OutOfRangeError("Input vapor quality [Q] must be between 0 and 1");
1424 break;
1425 case QSmolar_INPUTS:
1426 _Q = value1;
1427 _smolar = value2;
1428 if ((_Q < 0) || (_Q > 1)) throw CoolProp::OutOfRangeError("Input vapor quality [Q] must be between 0 and 1");
1430 break;
1431 case HmolarQ_INPUTS:
1432 _hmolar = value1;
1433 _Q = value2;
1434 if ((_Q < 0) || (_Q > 1)) throw CoolProp::OutOfRangeError("Input vapor quality [Q] must be between 0 and 1");
1436 break;
1437 case DmolarQ_INPUTS:
1438 _rhomolar = value1;
1439 _Q = value2;
1440 if ((_Q < 0) || (_Q > 1)) throw CoolProp::OutOfRangeError("Input vapor quality [Q] must be between 0 and 1");
1442 break;
1443 default:
1444 throw ValueError(format("This pair of inputs [%s] is not yet supported", get_input_pair_short_desc(input_pair).c_str()));
1445 }
1446
1447 post_update();
1448}
1449const std::vector<CoolPropDbl> HelmholtzEOSMixtureBackend::calc_mass_fractions() {
1450 // mass fraction is mass_i/total_mass;
1451 CoolPropDbl mm = molar_mass();
1452 std::vector<CoolPropDbl>& mole_fractions = get_mole_fractions_ref();
1453 std::vector<CoolPropDbl> mass_fractions(mole_fractions.size());
1454 for (std::size_t i = 0; i < mole_fractions.size(); ++i) {
1455 double mmi = get_fluid_constant(i, imolar_mass);
1456 mass_fractions[i] = mmi * (mole_fractions[i]) / mm;
1457 }
1458 return mass_fractions;
1459}
1460
1462 const GuessesStructure& guesses) {
1463 if (get_debug_level() > 10) {
1464 std::cout << format("%s (%d): update called with (%d: (%s), %g, %g)", __FILE__, __LINE__, input_pair,
1465 get_input_pair_short_desc(input_pair).c_str(), value1, value2)
1466 << std::endl;
1467 }
1468
1469 CoolPropDbl ld_value1 = value1, ld_value2 = value2;
1470 pre_update(input_pair, ld_value1, ld_value2);
1471 value1 = ld_value1;
1472 value2 = ld_value2;
1473
1474 switch (input_pair) {
1475 case PQ_INPUTS:
1476 _p = value1;
1477 _Q = value2;
1479 break;
1480 case QT_INPUTS:
1481 _Q = value1;
1482 _T = value2;
1484 break;
1485 case PT_INPUTS:
1486 _p = value1;
1487 _T = value2;
1489 break;
1490 default:
1491 throw ValueError(format("This pair of inputs [%s] is not yet supported", get_input_pair_short_desc(input_pair).c_str()));
1492 }
1493 post_update();
1494}
1495
1497 // Check the values that must always be set
1498 //if (_p < 0){ throw ValueError("p is less than zero");}
1499 if (!ValidNumber(_p)) {
1500 throw ValueError("p is not a valid number");
1501 }
1502 //if (_T < 0){ throw ValueError("T is less than zero");}
1503 if (!ValidNumber(_T)) {
1504 throw ValueError("T is not a valid number");
1505 }
1506 if (_rhomolar < 0) {
1507 throw ValueError("rhomolar is less than zero");
1508 }
1509 if (!ValidNumber(_rhomolar)) {
1510 throw ValueError("rhomolar is not a valid number");
1511 }
1512
1513 if (optional_checks) {
1514 if (!ValidNumber(_Q)) {
1515 throw ValueError("Q is not a valid number");
1516 }
1517 if (_phase == iphase_unknown) {
1518 throw ValueError("_phase is unknown");
1519 }
1520 }
1521
1522 // Set the reduced variables
1523 _tau = _reducing.T / _T;
1525
1526 // Update the terms in the excess contribution
1528 residual_helmholtz->Excess.update(_tau, _delta);
1529 }
1530}
1531
1533 return 1 / rhomolar_reducing() * calc_alphar_deriv_nocache(0, 1, mole_fractions, _tau, 1e-12);
1534}
1537 CoolPropDbl dtau_dT = -red.T / pow(_T, 2);
1538 return 1 / red.rhomolar * calc_alphar_deriv_nocache(1, 1, mole_fractions, _tau, 1e-12) * dtau_dT;
1539}
1541 return 1 / pow(rhomolar_reducing(), 2) * calc_alphar_deriv_nocache(0, 2, mole_fractions, _tau, 1e-12);
1542}
1545 CoolPropDbl dtau_dT = -red.T / pow(_T, 2);
1546 return 1 / pow(red.rhomolar, 2) * calc_alphar_deriv_nocache(1, 2, mole_fractions, _tau, 1e-12) * dtau_dT;
1547}
1549 /*
1550 Determine the phase given p and one other state variable
1551 */
1552 saturation_called = false;
1553
1554 // Reference declaration to save indexing
1555 CoolPropFluid& component = components[0];
1556
1557 // Maximum saturation temperature - Equal to critical pressure for pure fluids
1558 CoolPropDbl psat_max = calc_pmax_sat();
1559
1560 double T_crit_ = T_critical(), p_crit_ = p_critical(), rhomolar_crit_ = rhomolar_critical();
1561 auto smolar_critical = [this, &T_crit_, &rhomolar_crit_](){
1562 return this->calc_smolar_nocache(T_crit_, rhomolar_crit_);
1563 };
1564 auto hmolar_critical = [this, &T_crit_, &rhomolar_crit_](){
1565 return this->calc_hmolar_nocache(T_crit_, rhomolar_crit_);
1566 };
1567 auto umolar_critical = [this, &T_crit_, &rhomolar_crit_](){
1568 return this->calc_umolar_nocache(T_crit_, rhomolar_crit_);
1569 };
1570
1571 // Check supercritical pressure
1572 if (_p > psat_max) {
1573 _Q = 1e9;
1574 switch (other) {
1575 case iT: {
1576 if (_T > T_crit_) {
1578 return;
1579 } else {
1581 return;
1582 }
1583 }
1584 case iDmolar: {
1585 if (_rhomolar < rhomolar_crit_) {
1587 return;
1588 } else {
1590 return;
1591 }
1592 }
1593 case iSmolar: {
1594 if (_smolar.pt() > smolar_critical()) {
1596 return;
1597 } else {
1599 return;
1600 }
1601 }
1602 case iHmolar: {
1603 if (_hmolar.pt() > hmolar_critical()) {
1605 return;
1606 } else {
1608 return;
1609 }
1610 }
1611 case iUmolar: {
1612 if (_umolar.pt() > umolar_critical()) {
1614 return;
1615 } else {
1617 return;
1618 }
1619 }
1620 default: {
1621 throw ValueError("supercritical pressure but other invalid for now");
1622 }
1623 }
1624 }
1625 // Check between triple point pressure and psat_max
1626 else if (_p >= components[0].EOS().ptriple * 0.9999 && _p <= psat_max) {
1627
1628 // First try the superancillaries, use them to determine the state if you can
1629 if (get_config_bool(ENABLE_SUPERANCILLARIES) && is_pure()){
1630 auto& optsuperanc = get_superanc_optional();
1631 // Superancillaries are enabled and available, they will be used to determine the phase
1632 if (optsuperanc){
1633 auto& superanc = optsuperanc.value();
1634 CoolPropDbl pmax_num = superanc.get_pmax();
1635 if (_p > pmax_num){
1636 throw ValueError(format("Pressure to PQ_flash [%0.8Lg Pa] may not be above the numerical critical point of %0.15Lg Pa", _p, pmax_num));
1637 }
1638 auto Tsat = superanc.get_T_from_p(_p);
1639 auto rhoL = superanc.eval_sat(Tsat, 'D', 0);
1640 auto rhoV = superanc.eval_sat(Tsat, 'D', 1);
1641 auto psat = _p;
1642
1643 if (other == iT) {
1644 if (value < Tsat - 100 * DBL_EPSILON) {
1645 this->_phase = iphase_liquid;
1646 _Q = -1000;
1647 return;
1648 } else if (value > Tsat + 100 * DBL_EPSILON) {
1649 this->_phase = iphase_gas;
1650 _Q = 1000;
1651 return;
1652 } else {
1653 this->_phase = iphase_twophase;
1654 }
1655 }
1656 SatL->update_TDmolarP_unchecked(Tsat, rhoL, psat);
1657 SatV->update_TDmolarP_unchecked(Tsat, rhoV, psat);
1658 double Q;
1659 switch (other) {
1660 case iDmolar:
1661 Q = (1 / value - 1 / SatL->rhomolar()) / (1 / SatV->rhomolar() - 1 / SatL->rhomolar());
1662 break;
1663 case iSmolar:
1664 Q = (value - SatL->smolar()) / (SatV->smolar() - SatL->smolar());
1665 break;
1666 case iHmolar:
1667 Q = (value - SatL->hmolar()) / (SatV->hmolar() - SatL->hmolar());
1668 break;
1669 case iUmolar:
1670 Q = (value - SatL->umolar()) / (SatV->umolar() - SatL->umolar());
1671 break;
1672 default:
1673 throw ValueError(format("bad input for other"));
1674 }
1675 // Start off by setting variables based on assumption that the state is two-phase
1676 if (Q < -1e-9) {
1677 this->_phase = iphase_liquid;
1678 SatL->clear();
1679 SatV->clear();
1680 _Q = -1000;
1681 _TLanc = Tsat;
1682 } else if (Q > 1 + 1e-9) {
1683 this->_phase = iphase_gas;
1684 SatL->clear();
1685 SatV->clear();
1686 _Q = 1000;
1687 } else {
1688 _T = Tsat;
1689 _Q = Q;
1690 _rhomolar = 1 / (_Q / SatV->rhomolar() + (1 - _Q) / SatL->rhomolar());
1691 this->_phase = iphase_twophase;
1692 }
1693 return;
1694 }
1695 }
1696
1697
1698 // Then fall back to normal ancillaries, use them to determine the state if you can
1699
1700 // Calculate dew and bubble temps from the ancillaries (everything needs them)
1701 _TLanc = components[0].ancillaries.pL.invert(_p);
1702 _TVanc = components[0].ancillaries.pV.invert(_p);
1703
1704 bool definitely_two_phase = false;
1705
1706 // Try using the ancillaries for P,H,S if they are there
1707 switch (other) {
1708 case iT: {
1709
1710 if (has_melting_line()) {
1711 double Tm = melting_line(iT, iP, _p);
1712 if (get_config_bool(DONT_CHECK_PROPERTY_LIMITS)) {
1714 } else {
1715 if (_T < Tm - 0.001) {
1716 throw ValueError(format("For now, we don't support T [%g K] below Tmelt(p) [%g K]", _T, Tm));
1717 }
1718 }
1719 } else {
1720 if (get_config_bool(DONT_CHECK_PROPERTY_LIMITS)) {
1722 } else {
1723 if (_T < Tmin() - 0.001) {
1724 throw ValueError(format("For now, we don't support T [%g K] below Tmin(saturation) [%g K]", _T, Tmin()));
1725 }
1726 }
1727 }
1728
1729 CoolPropDbl T_vap = 0.1 + static_cast<double>(_TVanc);
1730 CoolPropDbl T_liq = -0.1 + static_cast<double>(_TLanc);
1731
1732 if (value > T_vap) {
1733 this->_phase = iphase_gas;
1734 _Q = -1000;
1735 return;
1736 } else if (value < T_liq) {
1737 this->_phase = iphase_liquid;
1738 _Q = 1000;
1739 return;
1740 }
1741 break;
1742 }
1743 case iHmolar: {
1744 if (!component.ancillaries.hL.enabled()) {
1745 break;
1746 }
1747 // Ancillaries are h-h_anchor, so add back h_anchor
1748 CoolPropDbl h_liq = component.ancillaries.hL.evaluate(_TLanc) + component.EOS().hs_anchor.hmolar;
1749 CoolPropDbl h_liq_error_band = component.ancillaries.hL.get_max_abs_error();
1750 CoolPropDbl h_vap = h_liq + component.ancillaries.hLV.evaluate(_TLanc);
1751 CoolPropDbl h_vap_error_band = h_liq_error_band + component.ancillaries.hLV.get_max_abs_error();
1752
1753 // HelmholtzEOSMixtureBackend HEOS(components);
1754 // HEOS.update(QT_INPUTS, 1, _TLanc);
1755 // double h1 = HEOS.hmolar();
1756 // HEOS.update(QT_INPUTS, 0, _TLanc);
1757 // double h0 = HEOS.hmolar();
1758
1759 // Check if in range given the accuracy of the fit
1760 if (value > h_vap + h_vap_error_band) {
1761 this->_phase = iphase_gas;
1762 _Q = -1000;
1763 return;
1764 } else if (value < h_liq - h_liq_error_band) {
1765 this->_phase = iphase_liquid;
1766 _Q = 1000;
1767 return;
1768 } else if (value > h_liq + h_liq_error_band && value < h_vap - h_vap_error_band) {
1769 definitely_two_phase = true;
1770 }
1771 break;
1772 }
1773 case iSmolar: {
1774 if (!component.ancillaries.sL.enabled()) {
1775 break;
1776 }
1777 // Ancillaries are s-s_anchor, so add back s_anchor
1778 CoolPropDbl s_anchor = component.EOS().hs_anchor.smolar;
1779 CoolPropDbl s_liq = component.ancillaries.sL.evaluate(_TLanc) + s_anchor;
1780 CoolPropDbl s_liq_error_band = component.ancillaries.sL.get_max_abs_error();
1781 CoolPropDbl s_vap = s_liq + component.ancillaries.sLV.evaluate(_TVanc);
1782 CoolPropDbl s_vap_error_band = s_liq_error_band + component.ancillaries.sLV.get_max_abs_error();
1783
1784 // Check if in range given the accuracy of the fit
1785 if (value > s_vap + s_vap_error_band) {
1786 this->_phase = iphase_gas;
1787 _Q = -1000;
1788 return;
1789 } else if (value < s_liq - s_liq_error_band) {
1790 this->_phase = iphase_liquid;
1791 _Q = 1000;
1792 return;
1793 } else if (value > s_liq + s_liq_error_band && value < s_vap - s_vap_error_band) {
1794 definitely_two_phase = true;
1795 }
1796 break;
1797 }
1798 case iUmolar: {
1799 if (!component.ancillaries.hL.enabled()) {
1800 break;
1801 }
1802 // u = h-p/rho
1803
1804 // Ancillaries are h-h_anchor, so add back h_anchor
1805 CoolPropDbl h_liq = component.ancillaries.hL.evaluate(_TLanc) + component.EOS().hs_anchor.hmolar;
1806 CoolPropDbl h_liq_error_band = component.ancillaries.hL.get_max_abs_error();
1807 CoolPropDbl h_vap = h_liq + component.ancillaries.hLV.evaluate(_TLanc);
1808 CoolPropDbl h_vap_error_band = h_liq_error_band + component.ancillaries.hLV.get_max_abs_error();
1809 CoolPropDbl rho_vap = component.ancillaries.rhoV.evaluate(_TVanc);
1810 CoolPropDbl rho_liq = component.ancillaries.rhoL.evaluate(_TLanc);
1811 CoolPropDbl u_liq = h_liq - _p / rho_liq;
1812 CoolPropDbl u_vap = h_vap - _p / rho_vap;
1813 CoolPropDbl u_liq_error_band = 1.5 * h_liq_error_band; // Most of error is in enthalpy
1814 CoolPropDbl u_vap_error_band = 1.5 * h_vap_error_band; // Most of error is in enthalpy
1815
1816 // Check if in range given the accuracy of the fit
1817 if (value > u_vap + u_vap_error_band) {
1818 this->_phase = iphase_gas;
1819 _Q = -1000;
1820 return;
1821 } else if (value < u_liq - u_liq_error_band) {
1822 this->_phase = iphase_liquid;
1823 _Q = 1000;
1824 return;
1825 } else if (value > u_liq + u_liq_error_band && value < u_vap - u_vap_error_band) {
1826 definitely_two_phase = true;
1827 }
1828 break;
1829 }
1830 default: {
1831 }
1832 }
1833
1834 // Now either density is an input, or an ancillary for h,s,u is missing
1835 // Always calculate the densities using the ancillaries
1836 if (!definitely_two_phase) {
1839 CoolPropDbl rho_vap = 0.95 * static_cast<double>(_rhoVanc);
1840 CoolPropDbl rho_liq = 1.05 * static_cast<double>(_rhoLanc);
1841 switch (other) {
1842 case iDmolar: {
1843 if (value < rho_vap) {
1844 this->_phase = iphase_gas;
1845 return;
1846 } else if (value > rho_liq) {
1847 this->_phase = iphase_liquid;
1848 return;
1849 }
1850 break;
1851 }
1852 }
1853 }
1854
1855 if (!is_pure_or_pseudopure) {
1856 throw ValueError("possibly two-phase inputs not supported for mixtures for now");
1857 }
1858
1859 // The slow full VLE calculation is required
1860 {
1861
1862 // Actually have to use saturation information sadly
1863 // For the given pressure, find the saturation state
1864 // Run the saturation routines to determine the saturation densities and pressures
1866 HEOS._p = this->_p;
1867 HEOS._Q = 0; // ?? What is the best to do here? Doesn't matter for our purposes since pure fluid
1869
1870 // We called the saturation routines, so HEOS.SatL and HEOS.SatV are now updated
1871 // with the saturated liquid and vapor values, which can therefore be used in
1872 // the other solvers
1873 saturation_called = true;
1874
1875 CoolPropDbl Q;
1876
1877 if (other == iT) {
1878 if (value < HEOS.SatL->T() - 100 * DBL_EPSILON) {
1879 this->_phase = iphase_liquid;
1880 _Q = -1000;
1881 return;
1882 } else if (value > HEOS.SatV->T() + 100 * DBL_EPSILON) {
1883 this->_phase = iphase_gas;
1884 _Q = 1000;
1885 return;
1886 } else {
1887 this->_phase = iphase_twophase;
1888 }
1889 }
1890 switch (other) {
1891 case iDmolar:
1892 Q = (1 / value - 1 / HEOS.SatL->rhomolar()) / (1 / HEOS.SatV->rhomolar() - 1 / HEOS.SatL->rhomolar());
1893 break;
1894 case iSmolar:
1895 Q = (value - HEOS.SatL->smolar()) / (HEOS.SatV->smolar() - HEOS.SatL->smolar());
1896 break;
1897 case iHmolar:
1898 Q = (value - HEOS.SatL->hmolar()) / (HEOS.SatV->hmolar() - HEOS.SatL->hmolar());
1899 break;
1900 case iUmolar:
1901 Q = (value - HEOS.SatL->umolar()) / (HEOS.SatV->umolar() - HEOS.SatL->umolar());
1902 break;
1903 default:
1904 throw ValueError(format("bad input for other"));
1905 }
1906 // TODO: Check the speed penalty of these calls
1907 // Update the states
1908 if (this->SatL) this->SatL->update(DmolarT_INPUTS, HEOS.SatL->rhomolar(), HEOS.SatL->T());
1909 if (this->SatV) this->SatV->update(DmolarT_INPUTS, HEOS.SatV->rhomolar(), HEOS.SatV->T());
1910 // Update the two-Phase variables
1911 _rhoLmolar = HEOS.SatL->rhomolar();
1912 _rhoVmolar = HEOS.SatV->rhomolar();
1913
1914 //
1915 if (Q < -1e-9) {
1916 this->_phase = iphase_liquid;
1917 _Q = -1000;
1918 return;
1919 } else if (Q > 1 + 1e-9) {
1920 this->_phase = iphase_gas;
1921 _Q = 1000;
1922 return;
1923 } else {
1924 this->_phase = iphase_twophase;
1925 }
1926
1927 _Q = Q;
1928 // Load the outputs
1929 _T = _Q * HEOS.SatV->T() + (1 - _Q) * HEOS.SatL->T();
1930 _rhomolar = 1 / (_Q / HEOS.SatV->rhomolar() + (1 - _Q) / HEOS.SatL->rhomolar());
1931 return;
1932 }
1933 } else if (_p < components[0].EOS().ptriple * 0.9999) {
1934 if (other == iT) {
1935 if (_T > std::max(Tmin(), Ttriple())) {
1937 } else {
1938 if (get_config_bool(DONT_CHECK_PROPERTY_LIMITS)) {
1940 } else {
1941 throw NotImplementedError(format("For now, we don't support p [%g Pa] below ptriple [%g Pa] when T [%g] is less than Tmin [%g]",
1942 _p, components[0].EOS().ptriple, _T, std::max(Tmin(), Ttriple())));
1943 }
1944 }
1945 } else {
1947 }
1948 } else {
1949 throw ValueError(format("The pressure [%g Pa] cannot be used in p_phase_determination", _p));
1950 }
1951}
1953 class Residual : public FuncWrapper1D
1954 {
1955 public:
1957 Residual(HelmholtzEOSMixtureBackend& HEOS) : HEOS(&HEOS){};
1958 double call(double T) {
1959 HEOS->update(QT_INPUTS, 1, T);
1960 // dTdp_along_sat
1961 double dTdp_along_sat =
1962 HEOS->T() * (1 / HEOS->SatV->rhomolar() - 1 / HEOS->SatL->rhomolar()) / (HEOS->SatV->hmolar() - HEOS->SatL->hmolar());
1963 // dsdT_along_sat;
1964 return HEOS->SatV->first_partial_deriv(iSmolar, iT, iP) + HEOS->SatV->first_partial_deriv(iSmolar, iP, iT) / dTdp_along_sat;
1965 }
1966 };
1968 shared_ptr<CoolProp::HelmholtzEOSMixtureBackend> HEOS_copy(new CoolProp::HelmholtzEOSMixtureBackend(get_components()));
1969 Residual resid(*HEOS_copy);
1970 const CoolProp::SimpleState& tripleV = HEOS_copy->get_components()[0].triple_vapor;
1971 double v1 = resid.call(hsat_max.T);
1972 double v2 = resid.call(tripleV.T);
1973 // If there is a sign change, there is a maxima, otherwise there is no local maxima/minima
1974 if (v1 * v2 < 0) {
1975 Brent(resid, hsat_max.T, tripleV.T, DBL_EPSILON, 1e-8, 30);
1976 ssat_max.T = resid.HEOS->T();
1977 ssat_max.p = resid.HEOS->p();
1978 ssat_max.rhomolar = resid.HEOS->rhomolar();
1979 ssat_max.hmolar = resid.HEOS->hmolar();
1980 ssat_max.smolar = resid.HEOS->smolar();
1982 } else {
1984 }
1985 }
1986}
1988 class Residualhmax : public FuncWrapper1D
1989 {
1990 public:
1992 Residualhmax(HelmholtzEOSMixtureBackend& HEOS) : HEOS(&HEOS){};
1993 double call(double T) {
1994 HEOS->update(QT_INPUTS, 1, T);
1995 // dTdp_along_sat
1996 double dTdp_along_sat =
1997 HEOS->T() * (1 / HEOS->SatV->rhomolar() - 1 / HEOS->SatL->rhomolar()) / (HEOS->SatV->hmolar() - HEOS->SatL->hmolar());
1998 // dhdT_along_sat;
1999 return HEOS->SatV->first_partial_deriv(iHmolar, iT, iP) + HEOS->SatV->first_partial_deriv(iHmolar, iP, iT) / dTdp_along_sat;
2000 }
2001 };
2002 if (!hsat_max.is_valid()) {
2003 shared_ptr<CoolProp::HelmholtzEOSMixtureBackend> HEOS_copy(new CoolProp::HelmholtzEOSMixtureBackend(get_components()));
2004 Residualhmax residhmax(*HEOS_copy);
2005 Brent(residhmax, T_critical() - 0.1, HEOS_copy->Ttriple() + 1, DBL_EPSILON, 1e-8, 30);
2006 hsat_max.T = residhmax.HEOS->T();
2007 hsat_max.p = residhmax.HEOS->p();
2008 hsat_max.rhomolar = residhmax.HEOS->rhomolar();
2009 hsat_max.hmolar = residhmax.HEOS->hmolar();
2010 hsat_max.smolar = residhmax.HEOS->smolar();
2011 }
2012}
2014 if (!ValidNumber(value)) {
2015 throw ValueError(format("value to T_phase_determination_pure_or_pseudopure is invalid"));
2016 };
2017
2018 double T_crit_ = T_critical(), p_crit_ = p_critical(), rhomolar_crit_ = rhomolar_critical();
2019 auto smolar_critical = [this, &T_crit_, &rhomolar_crit_](){
2020 return this->calc_smolar_nocache(T_crit_, rhomolar_crit_);
2021 };
2022 auto hmolar_critical = [this, &T_crit_, &rhomolar_crit_](){
2023 return this->calc_hmolar_nocache(T_crit_, rhomolar_crit_);
2024 };
2025 auto umolar_critical = [this, &T_crit_, &rhomolar_crit_](){
2026 return this->calc_umolar_nocache(T_crit_, rhomolar_crit_);
2027 };
2028
2029 // T is known, another input P, T, H, S, U is given (all molar)
2030 if (_T < T_crit_ && _p > p_crit_) {
2031 // Only ever true if (other = iP); otherwise _p = -HUGE
2033 } else if (std::abs(_T - T_crit_) < 10 * DBL_EPSILON) // Exactly at Tcrit
2034 {
2035 switch (other) {
2036 case iDmolar:
2037 if (std::abs(_rhomolar - rhomolar_crit_) < 10 * DBL_EPSILON) {
2039 break;
2040 } else if (_rhomolar > rhomolar_crit_) {
2042 break;
2043 } else {
2045 break;
2046 }
2047 case iP: {
2048 if (std::abs(_p - p_crit_) < 10 * DBL_EPSILON) {
2050 break;
2051 } else if (_p > p_crit_) {
2053 break;
2054 } else {
2056 break;
2057 }
2058 }
2059 default:
2060 throw ValueError(format("T=Tcrit; invalid input for other to T_phase_determination_pure_or_pseudopure"));
2061 }
2062 } else if (_T < T_crit_) // Gas, 2-Phase, Liquid, or Supercritical Liquid Region
2063 {
2064 if (get_config_bool(ENABLE_SUPERANCILLARIES) && is_pure()){
2065 auto& optsuperanc = get_superanc_optional();
2066 // Superancillaries are enabled and available, they will be used to determine the phase
2067 if (optsuperanc){
2068 auto& superanc = optsuperanc.value();
2069 auto rhoL = superanc.eval_sat(_T, 'D', 0);
2070 auto rhoV = superanc.eval_sat(_T, 'D', 1);
2071 auto psat = superanc.eval_sat(_T, 'P', 1);
2072 _rhoLanc = rhoL;
2073 _rhoVanc = rhoV;
2074 _rhoLmolar = rhoL;
2075 _rhoVmolar = rhoV;
2076
2077 if (other == iP){
2078 if (std::abs(psat/value-1) < 1e-6){
2079 throw ValueError(
2080 format("Saturation pressure [%g Pa] corresponding to T [%g K] is within 1e-4 %% of given p [%Lg Pa]", psat, _T, value)
2081 );
2082 }
2083 else if (value < psat) {
2085 _Q = -1000;
2086 } else if (value > psat) {
2088 _Q = 1000;
2089 }
2090 return;
2091 }
2092 double Q = -1;
2093 if (other == iDmolar){
2094 // Special case density as an input
2095 Q = (1 / value - 1 / rhoL) / (1 / rhoV - 1 / rhoL);
2096 if (Q <= 0) {
2098 _Q = -1000;
2099 } else if (Q >= 1) {
2101 _Q = 1000;
2102 } else {
2104 _p = psat;
2105 this->_Q = Q;
2106 SatL->update(DmolarT_INPUTS, rhoL, _T);
2107 SatV->update(DmolarT_INPUTS, rhoV, _T);
2108 }
2109 _rhomolar = value;
2110 return;
2111 }
2112
2113 SatL->update(DmolarT_INPUTS, rhoL, _T);
2114 SatV->update(DmolarT_INPUTS, rhoV, _T);
2115
2116 switch (other) {
2117 case iDmolar:
2118 Q = (1/value - 1/SatL->rhomolar()) / (1/SatV->rhomolar() - 1/SatL->rhomolar());
2119 break;
2120 case iSmolar:
2121 Q = (value - SatL->smolar()) / (SatV->smolar() - SatL->smolar());
2122 break;
2123 case iHmolar:
2124 Q = (value - SatL->hmolar()) / (SatV->hmolar() - SatL->hmolar());
2125 break;
2126 case iUmolar:
2127 Q = (value - SatL->umolar()) / (SatV->umolar() - SatL->umolar());
2128 break;
2129 default:
2130 throw ValueError(format("bad input for other"));
2131 }
2132
2133 if (Q < 0) {
2134 this->_phase = iphase_liquid;
2135 SatL->clear();
2136 SatV->clear();
2137 _Q = -1000;
2138
2139 } else if (Q > 1) {
2140 this->_phase = iphase_gas;
2141 SatL->clear();
2142 SatV->clear();
2143 _Q = 1000;
2144 } else {
2146 _p = psat;
2147 _Q = Q;
2148 _rhomolar = 1 / (_Q / rhoV + (1 - _Q) / rhoL);
2149 }
2150 return;
2151 }
2152 }
2153
2154
2155 // Start to think about the saturation stuff
2156 // First try to use the ancillary equations if you are far enough away
2157 // You know how accurate the ancillary equations are thanks to using CoolProp code to refit them
2158 switch (other) {
2159 case iP: {
2160 _pLanc = components[0].ancillaries.pL.evaluate(_T);
2161 _pVanc = components[0].ancillaries.pV.evaluate(_T);
2162 CoolPropDbl p_vap = 0.98 * static_cast<double>(_pVanc);
2163 CoolPropDbl p_liq = 1.02 * static_cast<double>(_pLanc);
2164
2165 if (value < p_vap) {
2166 this->_phase = iphase_gas;
2167 _Q = -1000;
2168 return;
2169 } else if (value > p_liq) {
2170 this->_phase = iphase_liquid;
2171 _Q = 1000;
2172 return;
2173 } else if (!is_pure()) // pseudo-pure
2174 {
2175 // For pseudo-pure fluids, the ancillary pressure curves are the official
2176 // arbiter of the phase
2177 if (value > static_cast<CoolPropDbl>(_pLanc)) {
2178 this->_phase = iphase_liquid;
2179 _Q = 1000;
2180 return;
2181 } else if (value < static_cast<CoolPropDbl>(_pVanc)) {
2182 this->_phase = iphase_gas;
2183 _Q = -1000;
2184 return;
2185 } else {
2186 throw ValueError("Two-phase inputs not supported for pseudo-pure for now");
2187 }
2188 }
2189 break;
2190 }
2191 default: {
2192 // Always calculate the densities using the ancillaries
2193 _rhoVanc = components[0].ancillaries.rhoV.evaluate(_T);
2194 _rhoLanc = components[0].ancillaries.rhoL.evaluate(_T);
2195 CoolPropDbl rho_vap = 0.95 * static_cast<double>(_rhoVanc);
2196 CoolPropDbl rho_liq = 1.05 * static_cast<double>(_rhoLanc);
2197 switch (other) {
2198 case iDmolar: {
2199 if (value < rho_vap) {
2200 this->_phase = iphase_gas;
2201 return;
2202 } else if (value > rho_liq) {
2203 this->_phase = iphase_liquid;
2204 return;
2205 } else {
2206 // Next we check the vapor quality based on the ancillary values
2207 double Qanc = (1 / value - 1 / static_cast<double>(_rhoLanc))
2208 / (1 / static_cast<double>(_rhoVanc) - 1 / static_cast<double>(_rhoLanc));
2209 // If the vapor quality is significantly inside the two-phase zone, stop, we are definitely two-phase
2210 if (value > 0.95 * rho_liq || value < 1.05 * rho_vap) {
2211 // Definitely single-phase
2212 _phase = iphase_liquid; // Needed for direct update call
2213 _Q = -1000; // Needed for direct update call
2214 update_DmolarT_direct(value, _T);
2215 CoolPropDbl pL = components[0].ancillaries.pL.evaluate(_T);
2216 if (Qanc < 0.01 && _p > pL * 1.05 && first_partial_deriv(iP, iDmolar, iT) > 0
2219 _Q = -1000;
2220 return;
2221 } else if (Qanc > 1.01) {
2222 break;
2223 } else {
2225 _p = _HUGE;
2226 }
2227 }
2228 }
2229 break;
2230 }
2231 default: {
2232 if (!this->SatL || !this->SatV) {
2233 throw ValueError(format("The saturation properties are needed in T_phase_determination_pure_or_pseudopure"));
2234 }
2235 // If it is not density, update the states
2236 SatV->update(DmolarT_INPUTS, rho_vap, _T);
2237 SatL->update(DmolarT_INPUTS, rho_liq, _T);
2238
2239 // First we check ancillaries
2240 switch (other) {
2241 case iSmolar: {
2242 if (value > SatV->calc_smolar()) {
2243 this->_phase = iphase_gas;
2244 return;
2245 }
2246 if (value < SatL->calc_smolar()) {
2247 this->_phase = iphase_liquid;
2248 return;
2249 }
2250 break;
2251 }
2252 case iHmolar: {
2253 if (value > SatV->calc_hmolar()) {
2254 this->_phase = iphase_gas;
2255 return;
2256 } else if (value < SatL->calc_hmolar()) {
2257 this->_phase = iphase_liquid;
2258 return;
2259 }
2260 break;
2261 }
2262 case iUmolar: {
2263 if (value > SatV->calc_umolar()) {
2264 this->_phase = iphase_gas;
2265 return;
2266 } else if (value < SatL->calc_umolar()) {
2267 this->_phase = iphase_liquid;
2268 return;
2269 }
2270 break;
2271 }
2272 default:
2273 throw ValueError(format("invalid input for other to T_phase_determination_pure_or_pseudopure"));
2274 }
2275 }
2276 }
2277 }
2278 }
2279
2280 // Actually have to use saturation information sadly
2281 // For the given temperature, find the saturation state
2282 // Run the saturation routines to determine the saturation densities and pressures
2286
2287 CoolPropDbl Q;
2288
2289 if (other == iP) {
2290 if (value > HEOS.SatL->p() * (1e-6 + 1)) {
2291 this->_phase = iphase_liquid;
2292 _Q = -1000;
2293 return;
2294 } else if (value < HEOS.SatV->p() * (1 - 1e-6)) {
2295 this->_phase = iphase_gas;
2296 _Q = 1000;
2297 return;
2298 } else {
2299 throw ValueError(
2300 format("Saturation pressure [%g Pa] corresponding to T [%g K] is within 1e-4 %% of given p [%Lg Pa]", HEOS.SatL->p(), _T, value));
2301 }
2302 }
2303
2304 switch (other) {
2305 case iDmolar:
2306 Q = (1 / value - 1 / HEOS.SatL->rhomolar()) / (1 / HEOS.SatV->rhomolar() - 1 / HEOS.SatL->rhomolar());
2307 break;
2308 case iSmolar:
2309 Q = (value - HEOS.SatL->smolar()) / (HEOS.SatV->smolar() - HEOS.SatL->smolar());
2310 break;
2311 case iHmolar:
2312 Q = (value - HEOS.SatL->hmolar()) / (HEOS.SatV->hmolar() - HEOS.SatL->hmolar());
2313 break;
2314 case iUmolar:
2315 Q = (value - HEOS.SatL->umolar()) / (HEOS.SatV->umolar() - HEOS.SatL->umolar());
2316 break;
2317 default:
2318 throw ValueError(format("bad input for other"));
2319 }
2320
2321 // Update the states
2322 if (this->SatL) this->SatL->update(DmolarT_INPUTS, HEOS.SatL->rhomolar(), HEOS.SatL->T());
2323 if (this->SatV) this->SatV->update(DmolarT_INPUTS, HEOS.SatV->rhomolar(), HEOS.SatV->T());
2324 // Update the two-Phase variables
2325 _rhoLmolar = HEOS.SatL->rhomolar();
2326 _rhoVmolar = HEOS.SatV->rhomolar();
2327
2328 if (Q < 0) {
2329 this->_phase = iphase_liquid;
2330 _Q = -1;
2331 return;
2332 } else if (Q > 1) {
2333 this->_phase = iphase_gas;
2334 _Q = 1;
2335 return;
2336 } else {
2337 this->_phase = iphase_twophase;
2338 }
2339 _Q = Q;
2340 // Load the outputs
2341 _p = _Q * HEOS.SatV->p() + (1 - _Q) * HEOS.SatL->p();
2342 _rhomolar = 1 / (_Q / HEOS.SatV->rhomolar() + (1 - _Q) / HEOS.SatL->rhomolar());
2343 return;
2344 } else if (_T > T_crit_ && _T > components[0].EOS().Ttriple) // Supercritical or Supercritical Gas Region
2345 {
2346 _Q = 1e9;
2347 switch (other) {
2348 case iP: {
2349 if (_p > p_crit_) {
2351 return;
2352 } else {
2354 return;
2355 }
2356 }
2357 case iDmolar: {
2358 if (_rhomolar > rhomolar_crit_) {
2360 return;
2361 } else {
2363 return;
2364 }
2365 }
2366 case iSmolar: {
2367 if (_smolar.pt() > smolar_critical()) {
2369 return;
2370 } else {
2372 return;
2373 }
2374 }
2375 case iHmolar: {
2376 if (_hmolar.pt() > hmolar_critical()) {
2378 return;
2379 } else {
2381 return;
2382 }
2383 }
2384 case iUmolar: {
2385 if (_umolar.pt() > umolar_critical()) {
2387 return;
2388 } else {
2390 return;
2391 }
2392 }
2393 default: {
2394 throw ValueError("supercritical temp but other invalid for now");
2395 }
2396 }
2397 } else {
2398 throw ValueError(format("For now, we don't support T [%g K] below Ttriple [%g K]", _T, components[0].EOS().Ttriple));
2399 }
2400}
2402 CoolPropDbl T = HEOS->T(), rho = HEOS->rhomolar(), rhor = HEOS->get_reducing_state().rhomolar, Tr = HEOS->get_reducing_state().T,
2403 dT_dtau = -pow(T, 2) / Tr, R = HEOS->gas_constant(), delta = rho / rhor, tau = Tr / T;
2404
2405 switch (index) {
2406 case iT:
2407 dT = 1;
2408 drho = 0;
2409 break;
2410 case iDmolar:
2411 dT = 0;
2412 drho = 1;
2413 break;
2414 case iDmass:
2415 dT = 0;
2416 drho = HEOS->molar_mass();
2417 break;
2418 case iP: {
2419 // dp/drho|T
2420 drho = R * T * (1 + 2 * delta * HEOS->dalphar_dDelta() + pow(delta, 2) * HEOS->d2alphar_dDelta2());
2421 // dp/dT|rho
2422 dT = rho * R * (1 + delta * HEOS->dalphar_dDelta() - tau * delta * HEOS->d2alphar_dDelta_dTau());
2423 break;
2424 }
2425 case iHmass:
2426 case iHmolar: {
2427 // dh/dT|rho
2428 dT = R
2429 * (-pow(tau, 2) * (HEOS->d2alpha0_dTau2() + HEOS->d2alphar_dTau2())
2430 + (1 + delta * HEOS->dalphar_dDelta() - tau * delta * HEOS->d2alphar_dDelta_dTau()));
2431 // dh/drhomolar|T
2432 drho =
2433 T * R / rho * (tau * delta * HEOS->d2alphar_dDelta_dTau() + delta * HEOS->dalphar_dDelta() + pow(delta, 2) * HEOS->d2alphar_dDelta2());
2434 if (index == iHmass) {
2435 // dhmolar/drhomolar|T * dhmass/dhmolar where dhmass/dhmolar = 1/mole mass
2436 drho /= HEOS->molar_mass();
2437 dT /= HEOS->molar_mass();
2438 }
2439 break;
2440 }
2441 case iSmass:
2442 case iSmolar: {
2443 // ds/dT|rho
2444 dT = R / T * (-pow(tau, 2) * (HEOS->d2alpha0_dTau2() + HEOS->d2alphar_dTau2()));
2445 // ds/drho|T
2446 drho = R / rho * (-(1 + delta * HEOS->dalphar_dDelta() - tau * delta * HEOS->d2alphar_dDelta_dTau()));
2447 if (index == iSmass) {
2448 // ds/drho|T / drhomass/drhomolar where drhomass/drhomolar = mole mass
2449 drho /= HEOS->molar_mass();
2450 dT /= HEOS->molar_mass();
2451 }
2452 break;
2453 }
2454 case iUmass:
2455 case iUmolar: {
2456 // du/dT|rho
2457 dT = R * (-pow(tau, 2) * (HEOS->d2alpha0_dTau2() + HEOS->d2alphar_dTau2()));
2458 // du/drho|T
2459 drho = HEOS->T() * R / rho * (tau * delta * HEOS->d2alphar_dDelta_dTau());
2460 if (index == iUmass) {
2461 // du/drho|T / drhomass/drhomolar where drhomass/drhomolar = mole mass
2462 drho /= HEOS->molar_mass();
2463 dT /= HEOS->molar_mass();
2464 }
2465 break;
2466 }
2467 case iTau:
2468 dT = 1 / dT_dtau;
2469 drho = 0;
2470 break;
2471 case iDelta:
2472 dT = 0;
2473 drho = 1 / rhor;
2474 break;
2475 default:
2476 throw ValueError(format("input to get_dT_drho[%s] is invalid", get_parameter_information(index, "short").c_str()));
2477 }
2478}
2479
2482 CoolPropDbl delta = rhomolar / reducing.rhomolar;
2483 CoolPropDbl tau = reducing.T / T;
2484
2485 // Calculate derivative
2486 int nTau = 0, nDelta = 1;
2488
2489 // Get pressure
2490 return rhomolar * gas_constant() * T * (1 + delta * dalphar_dDelta);
2491}
2493 CoolPropDbl& light, CoolPropDbl& heavy) {
2494
2496 class dpdrho_resid : public FuncWrapper1DWithTwoDerivs
2497 {
2498 public:
2500 CoolPropDbl T, p, delta, rhor, tau, R_u;
2501
2503 : HEOS(HEOS),
2504 T(T),
2505 p(p),
2506 delta(_HUGE),
2507 rhor(HEOS->get_reducing_state().rhomolar),
2508 tau(HEOS->get_reducing_state().T / T),
2509 R_u(HEOS->gas_constant()) {}
2510 double call(double rhomolar) {
2511 delta = rhomolar / rhor; // needed for derivative
2513 // dp/drho|T
2514 return R_u * T * (1 + 2 * delta * HEOS->dalphar_dDelta() + POW2(delta) * HEOS->d2alphar_dDelta2());
2515 };
2516 double deriv(double rhomolar) {
2517 // d2p/drho2|T
2518 return R_u * T / rhor * (2 * HEOS->dalphar_dDelta() + 4 * delta * HEOS->d2alphar_dDelta2() + POW2(delta) * HEOS->calc_d3alphar_dDelta3());
2519 };
2520 double second_deriv(double rhomolar) {
2521 // d3p/drho3|T
2522 return R_u * T / POW2(rhor)
2523 * (6 * HEOS->d2alphar_dDelta2() + 6 * delta * HEOS->d3alphar_dDelta3() + POW2(delta) * HEOS->calc_d4alphar_dDelta4());
2524 };
2525 };
2526 dpdrho_resid resid(this, T, p);
2527 light = -1;
2528 heavy = -1;
2529 try {
2530 light = Halley(resid, 1e-6, 1e-8, 100);
2531 double d2pdrho2__constT = resid.deriv(light);
2532 if (d2pdrho2__constT > 0) {
2533 // Not possible since curvature should be negative
2534 throw CoolProp::ValueError("curvature cannot be positive");
2535 }
2536 } catch (std::exception& e) {
2537 if (get_debug_level() > 5) {
2538 std::cout << e.what() << std::endl;
2539 };
2540 light = -1;
2541 }
2542
2543 if (light < 0) {
2544 try {
2545 // Now we are going to do something VERY slow - increase density until curvature is positive
2546 double rho = 1e-6;
2547 for (std::size_t counter = 0; counter <= 100; counter++) {
2548 resid.call(rho); // Updates the state
2549 double curvature = resid.deriv(rho);
2550 if (curvature > 0) {
2551 light = rho;
2552 break;
2553 }
2554 rho *= 2;
2555 }
2556 } catch (...) {
2557 }
2558 }
2559
2560 // First try a "normal" calculation of the stationary point on the liquid side
2561 for (double omega = 0.7; omega > 0; omega -= 0.2) {
2562 try {
2563 resid.options.add_number("omega", omega);
2564 heavy = Halley(resid, rhomax, 1e-8, 100);
2565 double d2pdrho2__constT = resid.deriv(heavy);
2566 if (d2pdrho2__constT < 0) {
2567 // Not possible since curvature should be positive
2568 throw CoolProp::ValueError("curvature cannot be negative");
2569 }
2570 break; // Jump out, we got a good solution
2571 } catch (std::exception& e) {
2572 if (get_debug_level() > 5) {
2573 std::cout << e.what() << std::endl;
2574 };
2575 heavy = -1;
2576 }
2577 }
2578
2579 if (heavy < 0) {
2580 try {
2581 // Now we are going to do something VERY slow - decrease density until curvature is negative or pressure is negative
2582 double rho = rhomax;
2583 for (std::size_t counter = 0; counter <= 100; counter++) {
2584 resid.call(rho); // Updates the state
2585 double curvature = resid.deriv(rho);
2586 if (curvature < 0 || this->p() < 0) {
2587 heavy = rho;
2588 break;
2589 }
2590 rho /= 1.1;
2591 }
2592 } catch (...) {
2593 }
2594 }
2595
2596 if (light > 0 && heavy > 0) {
2597 // Found two stationary points, done!
2599 }
2600 // If no solution is found for dpdrho|T=0 starting at high and low densities,
2601 // then try to do a bounded solver to see if you can find any solutions. If you
2602 // can't, p = f(rho) is probably monotonic (supercritical?), and the bounds are
2603 else if (light < 0 && heavy < 0) {
2604 double dpdrho_min = resid.call(1e-10);
2605 double dpdrho_max = resid.call(rhomax);
2606 if (dpdrho_max * dpdrho_min > 0) {
2608 } else {
2609 throw CoolProp::ValueError("zero stationary points -- does this make sense?");
2610 }
2611 } else {
2613 }
2614}
2615// Define the residual to be driven to zero
2617{
2618 public:
2621
2623 : HEOS(HEOS),
2624 T(T),
2625 p(p),
2626 delta(_HUGE),
2627 rhor(HEOS->get_reducing_state().rhomolar),
2628 tau(HEOS->get_reducing_state().T / T),
2629 R_u(HEOS->gas_constant()) {}
2630 double call(double rhomolar) {
2631 delta = rhomolar / rhor; // needed for derivative
2632 HEOS->update_DmolarT_direct(rhomolar, T);
2633 CoolPropDbl peos = HEOS->p();
2634 return (peos - p) / p;
2635 };
2636 double deriv(double rhomolar) {
2637 // dp/drho|T / pspecified
2638 return R_u * T * (1 + 2 * delta * HEOS->dalphar_dDelta() + POW2(delta) * HEOS->d2alphar_dDelta2()) / p;
2639 };
2640 double second_deriv(double rhomolar) {
2641 // d2p/drho2|T / pspecified
2642 return R_u * T / rhor * (2 * HEOS->dalphar_dDelta() + 4 * delta * HEOS->d2alphar_dDelta2() + POW2(delta) * HEOS->calc_d3alphar_dDelta3()) / p;
2643 };
2644 double third_deriv(double rhomolar) {
2645 // d3p/drho3|T / pspecified
2646 return R_u * T / POW2(rhor)
2648 };
2649};
2651 double b = 0;
2652 for (std::size_t i = 0; i < mole_fractions.size(); ++i) {
2653 // Get the parameters for the cubic EOS
2655 CoolPropDbl R = 8.3144598;
2656 b += mole_fractions[i] * 0.08664 * R * Tc / pc;
2657 }
2658 return b;
2659}
2661 // Find the densities along the isotherm where dpdrho|T = 0 (if you can)
2662 CoolPropDbl light = -1, heavy = -1;
2663 StationaryPointReturnFlag retval = solver_dpdrho0_Tp(T, p, rhomolar_max, light, heavy);
2664
2665 // Define the solver class
2666 SolverTPResid resid(this, T, p);
2667
2668 if (retval == ZERO_STATIONARY_POINTS) {
2669 // It's monotonic (no stationary points found), so do the full bounded solver
2670 // for the density
2671 double rho = Brent(resid, 1e-10, rhomolar_max, DBL_EPSILON, 1e-8, 100);
2672 return rho;
2673 } else if (retval == TWO_STATIONARY_POINTS_FOUND) {
2674
2675 // Calculate the pressures at the min and max densities where dpdrho|T = 0
2676 double p_at_rhomin_stationary = calc_pressure_nocache(T, light);
2677 double p_at_rhomax_stationary = calc_pressure_nocache(T, heavy);
2678
2679 double rho_liq = -1, rho_vap = -1;
2680 if (p > p_at_rhomax_stationary) {
2681 int counter = 0;
2682 for (/* init above, for debugging */; counter <= 10; counter++) {
2683 // Bump up rhomax if needed to bound the given pressure
2684 double p_at_rhomax = calc_pressure_nocache(T, rhomolar_max);
2685 if (p_at_rhomax < p) {
2686 rhomolar_max *= 1.05;
2687 } else {
2688 break;
2689 }
2690 }
2691 // Look for liquid root starting at stationary point density
2692 rho_liq = Brent(resid, heavy, rhomolar_max, DBL_EPSILON, 1e-8, 100);
2693 }
2694
2695 if (p < p_at_rhomin_stationary) {
2696 // Look for vapor root starting at stationary point density
2697 rho_vap = Brent(resid, light, 1e-10, DBL_EPSILON, 1e-8, 100);
2698 }
2699
2700 if (rho_vap > 0 && rho_liq > 0) {
2701 // Both densities are the same
2702 if (std::abs(rho_vap - rho_liq) < 1e-10) {
2703 // return one of them
2704 return rho_vap;
2705 } else {
2706 // Two solutions found, keep the one with lower Gibbs energy
2707 double gibbsmolar_vap = calc_gibbsmolar_nocache(T, rho_vap);
2708 double gibbsmolar_liq = calc_gibbsmolar_nocache(T, rho_liq);
2709 if (gibbsmolar_liq < gibbsmolar_vap) {
2710 return rho_liq;
2711 } else {
2712 return rho_vap;
2713 }
2714 }
2715 } else if (rho_vap < 0 && rho_liq > 0) {
2716 // Liquid root found, return it
2717 return rho_liq;
2718 } else if (rho_vap > 0 && rho_liq < 0) {
2719 // Vapor root found, return it
2720 return rho_vap;
2721 } else {
2722 throw CoolProp::ValueError(format("No density solutions for T=%g,p=%g,z=%s", T, p, vec_to_string(static_cast<std::vector<double>>(mole_fractions), "%0.12g").c_str()));
2723 }
2724 } else {
2726 format("One stationary point (not good) for T=%g,p=%g,z=%s", T, p, vec_to_string(static_cast<std::vector<double>>(mole_fractions), "%0.12g").c_str()));
2727 }
2728};
2729
2731 phases phase;
2732
2733 SolverTPResid resid(this, T, p);
2734
2735 // Check if the phase is imposed
2737 // Use the imposed phase index
2739 else
2740 // Use the phase index in the class
2741 phase = _phase;
2742
2743 if (rhomolar_guess < 0) // Not provided
2744 {
2745 // Calculate a guess value using SRK equation of state
2746 rhomolar_guess = solver_rho_Tp_SRK(T, p, phase);
2747
2748 // A gas-like phase, ideal gas might not be the perfect model, but probably good enough
2750 if (rhomolar_guess < 0 || !ValidNumber(rhomolar_guess)) // If the guess is bad, probably high temperature, use ideal gas
2751 {
2752 rhomolar_guess = p / (gas_constant() * T);
2753 }
2754 } else if (phase == iphase_liquid) {
2755 double rhomolar;
2757 // It's liquid at subcritical pressure, we can use ancillaries as guess value
2758 CoolPropDbl _rhoLancval = static_cast<CoolPropDbl>(components[0].ancillaries.rhoL.evaluate(T));
2759 try {
2760 // First we try with Halley's method starting at saturated liquid
2761 rhomolar = Halley(resid, _rhoLancval, 1e-8, 100);
2764 throw ValueError("Liquid density is invalid");
2765 }
2766 } catch (std::exception&) {
2767 // Next we try with a Brent method bounded solver since the function is 1-1
2768 rhomolar = Brent(resid, _rhoLancval * 0.9, _rhoLancval * 1.3, DBL_EPSILON, 1e-8, 100);
2769 if (!ValidNumber(rhomolar)) {
2770 throw ValueError();
2771 }
2772 }
2773 } else {
2774 // Try with 4th order Householder method starting at a very high density
2775 rhomolar = Householder4(&resid, 3 * rhomolar_reducing(), 1e-8, 100);
2776 }
2777 return rhomolar;
2778 } else if (phase == iphase_supercritical_liquid) {
2779 CoolPropDbl rhoLancval = static_cast<CoolPropDbl>(components[0].ancillaries.rhoL.evaluate(T));
2780 // Next we try with a Brent method bounded solver since the function is 1-1
2781 double rhomolar = Brent(resid, rhoLancval * 0.99, rhomolar_critical() * 4, DBL_EPSILON, 1e-8, 100);
2782 if (!ValidNumber(rhomolar)) {
2783 throw ValueError();
2784 }
2785 return rhomolar;
2786 }
2787 }
2788
2789 try {
2790 double rhomolar = Householder4(resid, rhomolar_guess, 1e-8, 20);
2791 if (!ValidNumber(rhomolar) || rhomolar < 0) {
2792 throw ValueError();
2793 }
2794 if (phase == iphase_liquid) {
2795 double dpdrho = first_partial_deriv(iP, iDmolar, iT);
2796 double d2pdrho2 = second_partial_deriv(iP, iDmolar, iT, iDmolar, iT);
2797 if (dpdrho < 0 || d2pdrho2 < 0) {
2798 // Try again with a larger density in order to end up at the right solution
2799 rhomolar = Householder4(resid, 3 * rhomolar_reducing(), 1e-8, 100);
2800 return rhomolar;
2801 }
2802 } else if (phase == iphase_gas) {
2803 double dpdrho = first_partial_deriv(iP, iDmolar, iT);
2804 double d2pdrho2 = second_partial_deriv(iP, iDmolar, iT, iDmolar, iT);
2805 if (dpdrho < 0 || d2pdrho2 > 0) {
2806 // Try again with a tiny density in order to end up at the right solution
2807 rhomolar = Householder4(resid, 1e-6, 1e-8, 100);
2808 return rhomolar;
2809 }
2810 }
2811 return rhomolar;
2812 } catch (std::exception& e) {
2814 double rhomolar = Brent(resid, 1e-10, 3 * rhomolar_reducing(), DBL_EPSILON, 1e-8, 100);
2815 return rhomolar;
2816 } else if (is_pure_or_pseudopure && T > T_critical()) {
2817 try {
2818 double rhomolar = Brent(resid, 1e-10, 5 * rhomolar_reducing(), DBL_EPSILON, 1e-8, 100);
2819 return rhomolar;
2820
2821 } catch (...) {
2822 double rhomolar = Householder4(resid, 3 * rhomolar_reducing(), 1e-8, 100);
2823 return rhomolar;
2824 }
2825 }
2826 throw ValueError(format("solver_rho_Tp was unable to find a solution for T=%10Lg, p=%10Lg, with guess value %10Lg with error: %s", T, p,
2827 rhomolar_guess, e.what()));
2828 }
2829}
2831 CoolPropDbl rhomolar, R_u = gas_constant(), a = 0, b = 0, k_ij = 0;
2832
2833 for (std::size_t i = 0; i < components.size(); ++i) {
2834 CoolPropDbl Tci = components[i].EOS().reduce.T, pci = components[i].EOS().reduce.p, acentric_i = components[i].EOS().acentric;
2835 CoolPropDbl m_i = 0.480 + 1.574 * acentric_i - 0.176 * pow(acentric_i, 2);
2836 CoolPropDbl b_i = 0.08664 * R_u * Tci / pci;
2837 b += mole_fractions[i] * b_i;
2838
2839 CoolPropDbl a_i = 0.42747 * pow(R_u * Tci, 2) / pci * pow(1 + m_i * (1 - sqrt(T / Tci)), 2);
2840
2841 for (std::size_t j = 0; j < components.size(); ++j) {
2842 CoolPropDbl Tcj = components[j].EOS().reduce.T, pcj = components[j].EOS().reduce.p, acentric_j = components[j].EOS().acentric;
2843 CoolPropDbl m_j = 0.480 + 1.574 * acentric_j - 0.176 * pow(acentric_j, 2);
2844
2845 CoolPropDbl a_j = 0.42747 * pow(R_u * Tcj, 2) / pcj * pow(1 + m_j * (1 - sqrt(T / Tcj)), 2);
2846
2847 k_ij = 0;
2848 //if (i == j){
2849 // k_ij = 0;
2850 //}
2851 //else{
2852 // k_ij = 0;
2853 //}
2854
2855 a += mole_fractions[i] * mole_fractions[j] * sqrt(a_i * a_j) * (1 - k_ij);
2856 }
2857 }
2858
2859 CoolPropDbl A = a * p / pow(R_u * T, 2);
2860 CoolPropDbl B = b * p / (R_u * T);
2861
2862 //Solve the cubic for solutions for Z = p/(rho*R*T)
2863 double Z0, Z1, Z2;
2864 int Nsolns;
2865 solve_cubic(1, -1, A - B - B * B, -A * B, Nsolns, Z0, Z1, Z2);
2866
2867 // Determine the guess value
2868 if (Nsolns == 1) {
2869 rhomolar = p / (Z0 * R_u * T);
2870 } else {
2871 CoolPropDbl rhomolar0 = p / (Z0 * R_u * T);
2872 CoolPropDbl rhomolar1 = p / (Z1 * R_u * T);
2873 CoolPropDbl rhomolar2 = p / (Z2 * R_u * T);
2874
2875 // Check if only one solution is positive, return the solution if that is the case
2876 if (rhomolar0 > 0 && rhomolar1 <= 0 && rhomolar2 <= 0) {
2877 return rhomolar0;
2878 }
2879 if (rhomolar0 <= 0 && rhomolar1 > 0 && rhomolar2 <= 0) {
2880 return rhomolar1;
2881 }
2882 if (rhomolar0 <= 0 && rhomolar1 <= 0 && rhomolar2 > 0) {
2883 return rhomolar2;
2884 }
2885
2886 switch (phase) {
2887 case iphase_liquid:
2889 rhomolar = max3(rhomolar0, rhomolar1, rhomolar2);
2890 break;
2891 case iphase_gas:
2894 rhomolar = min3(rhomolar0, rhomolar1, rhomolar2);
2895 break;
2896 default:
2897 throw ValueError("Bad phase to solver_rho_Tp_SRK");
2898 };
2899 }
2900 return rhomolar;
2901}
2902
2904 // Calculate the reducing parameters
2906 _tau = _reducing.T / _T;
2907
2908 // Calculate derivative if needed
2909 CoolPropDbl dar_dDelta = dalphar_dDelta();
2910 CoolPropDbl R_u = gas_constant();
2911
2912 // Get pressure
2913 _p = _rhomolar * R_u * _T * (1 + _delta.pt() * dar_dDelta);
2914
2915 //std::cout << format("p: %13.12f %13.12f %10.9f %10.9f %10.9f %10.9f %g\n",_T,_rhomolar,_tau,_delta,mole_fractions[0],dar_dDelta,_p);
2916 //if (_p < 0){
2917 // throw ValueError("Pressure is less than zero");
2918 //}
2919
2920 return static_cast<CoolPropDbl>(_p);
2921}
2923 // Calculate the reducing parameters
2926
2927 // Calculate derivatives if needed, or just use cached values
2928 // Calculate derivative if needed
2932 CoolPropDbl R_u = gas_constant();
2933
2934 // Get molar enthalpy
2935 return R_u * T * (1 + tau * (da0_dTau + dar_dTau) + delta * dar_dDelta);
2936}
2938 if (get_debug_level() >= 50)
2939 std::cout << format("HelmholtzEOSMixtureBackend::calc_hmolar: 2phase: %d T: %g rhomomolar: %g", isTwoPhase(), _T, _rhomolar) << std::endl;
2940 if (isTwoPhase()) {
2941 if (!this->SatL || !this->SatV) throw ValueError(format("The saturation properties are needed for the two-phase properties"));
2942 if (std::abs(_Q) < DBL_EPSILON) {
2943 _hmolar = SatL->hmolar();
2944 } else if (std::abs(_Q - 1) < DBL_EPSILON) {
2945 _hmolar = SatV->hmolar();
2946 } else {
2947 _hmolar = _Q * SatV->hmolar() + (1 - _Q) * SatL->hmolar();
2948 }
2949 return static_cast<CoolPropDbl>(_hmolar);
2950 } else if (isHomogeneousPhase()) {
2951 // Calculate the reducing parameters
2953 _tau = _reducing.T / _T;
2954
2955 // Calculate derivatives if needed, or just use cached values
2956 CoolPropDbl da0_dTau = dalpha0_dTau();
2957 CoolPropDbl dar_dTau = dalphar_dTau();
2958 CoolPropDbl dar_dDelta = dalphar_dDelta();
2959 CoolPropDbl R_u = gas_constant();
2960
2961 // Get molar enthalpy
2962 _hmolar = R_u * _T * (1 + _tau.pt() * (da0_dTau + dar_dTau) + _delta.pt() * dar_dDelta);
2963
2964 return static_cast<CoolPropDbl>(_hmolar);
2965 } else {
2966 throw ValueError(format("phase is invalid in calc_hmolar"));
2967 }
2968}
2970 // Calculate the reducing parameters
2973
2974 // Calculate derivatives if needed, or just use cached values
2975 // Calculate derivative if needed
2980 CoolPropDbl R_u = gas_constant();
2981
2982 // Get molar entropy
2983 return R_u * (tau * (da0_dTau + dar_dTau) - a0 - ar);
2984}
2986 if (isTwoPhase()) {
2987 if (!this->SatL || !this->SatV) throw ValueError(format("The saturation properties are needed for the two-phase properties"));
2988 if (std::abs(_Q) < DBL_EPSILON) {
2989 _smolar = SatL->smolar();
2990 } else if (std::abs(_Q - 1) < DBL_EPSILON) {
2991 _smolar = SatV->smolar();
2992 } else {
2993 _smolar = _Q * SatV->smolar() + (1 - _Q) * SatL->smolar();
2994 }
2995 return static_cast<CoolPropDbl>(_smolar);
2996 } else if (isHomogeneousPhase()) {
2997 // Calculate the reducing parameters
2999 _tau = _reducing.T / _T;
3000
3001 // Calculate derivatives if needed, or just use cached values
3002 CoolPropDbl da0_dTau = dalpha0_dTau();
3003 CoolPropDbl ar = alphar();
3004 CoolPropDbl a0 = alpha0();
3005 CoolPropDbl dar_dTau = dalphar_dTau();
3006 CoolPropDbl R_u = gas_constant();
3007
3008 // Get molar entropy
3009 _smolar = R_u * (_tau.pt() * (da0_dTau + dar_dTau) - a0 - ar);
3010
3011 return static_cast<CoolPropDbl>(_smolar);
3012 } else {
3013 throw ValueError(format("phase is invalid in calc_smolar"));
3014 }
3015}
3017 // Calculate the reducing parameters
3020
3021 // Calculate derivatives
3024 CoolPropDbl R_u = gas_constant();
3025
3026 // Get molar internal energy
3027 return R_u * T * tau * (da0_dTau + dar_dTau);
3028}
3030 if (isTwoPhase()) {
3031 if (!this->SatL || !this->SatV) throw ValueError(format("The saturation properties are needed for the two-phase properties"));
3032 if (std::abs(_Q) < DBL_EPSILON) {
3033 _umolar = SatL->umolar();
3034 } else if (std::abs(_Q - 1) < DBL_EPSILON) {
3035 _umolar = SatV->umolar();
3036 } else {
3037 _umolar = _Q * SatV->umolar() + (1 - _Q) * SatL->umolar();
3038 }
3039 return static_cast<CoolPropDbl>(_umolar);
3040 } else if (isHomogeneousPhase()) {
3041 // Calculate the reducing parameters
3043 _tau = _reducing.T / _T;
3044
3045 // Calculate derivatives if needed, or just use cached values
3046 CoolPropDbl da0_dTau = dalpha0_dTau();
3047 CoolPropDbl dar_dTau = dalphar_dTau();
3048 CoolPropDbl R_u = gas_constant();
3049
3050 // Get molar internal energy
3051 _umolar = R_u * _T * _tau.pt() * (da0_dTau + dar_dTau);
3052
3053 return static_cast<CoolPropDbl>(_umolar);
3054 } else {
3055 throw ValueError(format("phase is invalid in calc_umolar"));
3056 }
3057}
3059 // Calculate the reducing parameters
3061 _tau = _reducing.T / _T;
3062
3063 // Calculate derivatives if needed, or just use cached values
3064 CoolPropDbl d2ar_dTau2 = d2alphar_dTau2();
3065 CoolPropDbl d2a0_dTau2 = d2alpha0_dTau2();
3066 CoolPropDbl R_u = gas_constant();
3067
3068 // Get cv
3069 _cvmolar = -R_u * pow(_tau.pt(), 2) * (d2ar_dTau2 + d2a0_dTau2);
3070
3071 return static_cast<double>(_cvmolar);
3072}
3074 // Calculate the reducing parameters
3076 _tau = _reducing.T / _T;
3077
3078 // Calculate derivatives if needed, or just use cached values
3079 CoolPropDbl d2a0_dTau2 = d2alpha0_dTau2();
3080 CoolPropDbl dar_dDelta = dalphar_dDelta();
3081 CoolPropDbl d2ar_dDelta2 = d2alphar_dDelta2();
3082 CoolPropDbl d2ar_dDelta_dTau = d2alphar_dDelta_dTau();
3083 CoolPropDbl d2ar_dTau2 = d2alphar_dTau2();
3084 CoolPropDbl R_u = gas_constant();
3085
3086 // Get cp
3087 _cpmolar = R_u
3088 * (-pow(_tau.pt(), 2) * (d2ar_dTau2 + d2a0_dTau2)
3089 + pow(1 + _delta.pt() * dar_dDelta - _delta.pt() * _tau.pt() * d2ar_dDelta_dTau, 2)
3090 / (1 + 2 * _delta.pt() * dar_dDelta + pow(_delta.pt(), 2) * d2ar_dDelta2));
3091
3092 return static_cast<double>(_cpmolar);
3093}
3095 // Calculate the reducing parameters
3097 _tau = _reducing.T / _T;
3098
3099 // Calculate derivatives if needed, or just use cached values
3100 CoolPropDbl d2a0_dTau2 = d2alpha0_dTau2();
3101 CoolPropDbl R_u = gas_constant();
3102
3103 // Get cp of the ideal gas
3104 return R_u * (1 + (-pow(_tau.pt(), 2)) * d2a0_dTau2);
3105}
3107 if (isTwoPhase()) {
3108 if (std::abs(_Q) < DBL_EPSILON) {
3109 return SatL->speed_sound();
3110 } else if (std::abs(_Q - 1) < DBL_EPSILON) {
3111 return SatV->speed_sound();
3112 } else {
3113 throw ValueError(format("Speed of sound is not defined for two-phase states because it depends on the distribution of phases."));
3114 }
3115 } else if (isHomogeneousPhase()) {
3116 // Calculate the reducing parameters
3118 _tau = _reducing.T / _T;
3119
3120 // Calculate derivatives if needed, or just use cached values
3121 CoolPropDbl d2a0_dTau2 = d2alpha0_dTau2();
3122 CoolPropDbl dar_dDelta = dalphar_dDelta();
3123 CoolPropDbl d2ar_dDelta2 = d2alphar_dDelta2();
3124 CoolPropDbl d2ar_dDelta_dTau = d2alphar_dDelta_dTau();
3125 CoolPropDbl d2ar_dTau2 = d2alphar_dTau2();
3126 CoolPropDbl R_u = gas_constant();
3127 CoolPropDbl mm = molar_mass();
3128
3129 // Get speed of sound
3130 _speed_sound = sqrt(
3131 R_u * _T / mm
3132 * (1 + 2 * _delta.pt() * dar_dDelta + pow(_delta.pt(), 2) * d2ar_dDelta2
3133 - pow(1 + _delta.pt() * dar_dDelta - _delta.pt() * _tau.pt() * d2ar_dDelta_dTau, 2) / (pow(_tau.pt(), 2) * (d2ar_dTau2 + d2a0_dTau2))));
3134
3135 return static_cast<CoolPropDbl>(_speed_sound);
3136 } else {
3137 throw ValueError(format("phase is invalid in calc_speed_sound"));
3138 }
3139}
3140
3142 // Calculate the reducing parameters
3145
3146 // Calculate derivatives
3150
3151 // Get molar gibbs function
3152 return gas_constant() * T * (1 + a0 + ar + delta * dar_dDelta);
3153}
3155 if (isTwoPhase()) {
3156 if (!this->SatL || !this->SatV) throw ValueError(format("The saturation properties are needed for the two-phase properties"));
3157 _gibbsmolar = _Q * SatV->gibbsmolar() + (1 - _Q) * SatL->gibbsmolar();
3158 return static_cast<CoolPropDbl>(_gibbsmolar);
3159 } else if (isHomogeneousPhase()) {
3160 // Calculate the reducing parameters
3162 _tau = _reducing.T / _T;
3163
3164 // Calculate derivatives if needed, or just use cached values
3165 CoolPropDbl ar = alphar();
3166 CoolPropDbl a0 = alpha0();
3167 CoolPropDbl dar_dDelta = dalphar_dDelta();
3168 CoolPropDbl R_u = gas_constant();
3169
3170 // Get molar gibbs function
3171 _gibbsmolar = R_u * _T * (1 + a0 + ar + _delta.pt() * dar_dDelta);
3172
3173 return static_cast<CoolPropDbl>(_gibbsmolar);
3174 } else {
3175 throw ValueError(format("phase is invalid in calc_gibbsmolar"));
3176 }
3177}
3180 _umolar_excess = this->umolar();
3181 _volumemolar_excess = 1 / this->rhomolar();
3182 for (std::size_t i = 0; i < components.size(); ++i) {
3184 transient_pure_state->update(PT_INPUTS, p(), T());
3185 double x_i = mole_fractions[i];
3186 double R = gas_constant();
3187 _gibbsmolar_excess = static_cast<double>(_gibbsmolar_excess) - x_i * (transient_pure_state->gibbsmolar() + R * T() * log(x_i));
3188 _hmolar_excess = static_cast<double>(_hmolar_excess) - x_i * transient_pure_state->hmolar();
3189 _umolar_excess = static_cast<double>(_umolar_excess) - x_i * transient_pure_state->umolar();
3190 _smolar_excess = static_cast<double>(_smolar_excess) - x_i * (transient_pure_state->smolar() - R * log(x_i));
3191 _volumemolar_excess = static_cast<double>(_volumemolar_excess) - x_i / transient_pure_state->rhomolar();
3192 }
3193 _helmholtzmolar_excess = static_cast<double>(_umolar_excess) - _T * static_cast<double>(_smolar_excess);
3194}
3195
3197 if (isTwoPhase()) {
3198 if (!this->SatL || !this->SatV) throw ValueError(format("The saturation properties are needed for the two-phase properties"));
3199 _helmholtzmolar = _Q * SatV->helmholtzmolar() + (1 - _Q) * SatL->helmholtzmolar();
3200 return static_cast<CoolPropDbl>(_helmholtzmolar);
3201 } else if (isHomogeneousPhase()) {
3202 // Calculate the reducing parameters
3204 _tau = _reducing.T / _T;
3205
3206 // Calculate derivatives if needed, or just use cached values
3207 CoolPropDbl ar = alphar();
3208 CoolPropDbl a0 = alpha0();
3209 CoolPropDbl R_u = gas_constant();
3210
3211 // Get molar Helmholtz energy
3212 _helmholtzmolar = R_u * _T * (a0 + ar);
3213
3214 return static_cast<CoolPropDbl>(_helmholtzmolar);
3215 } else {
3216 throw ValueError(format("phase is invalid in calc_helmholtzmolar"));
3217 }
3218}
3221 return exp(MixtureDerivatives::ln_fugacity_coefficient(*this, i, xN_flag));
3222}
3225 return MixtureDerivatives::fugacity_i(*this, i, xN_flag);
3226}
3229 double Tci = get_fluid_constant(i, iT_critical);
3230 double rhoci = get_fluid_constant(i, irhomolar_critical);
3231 double dnar_dni__const_T_V_nj = MixtureDerivatives::dnalphar_dni__constT_V_nj(*this, i, xN_flag);
3232 double dna0_dni__const_T_V_nj =
3233 components[i].EOS().alpha0.base(tau() * (Tci / T_reducing()), delta() / (rhoci / rhomolar_reducing())) + 1 + log(mole_fractions[i]);
3234 return gas_constant() * T() * (dna0_dni__const_T_V_nj + dnar_dni__const_T_V_nj);
3235}
3237 return 2
3238 - rhomolar()
3241}
3242
3243SimpleState HelmholtzEOSMixtureBackend::calc_reducing_state_nocache(const std::vector<CoolPropDbl>& mole_fractions) {
3244 SimpleState reducing;
3246 reducing = components[0].EOS().reduce;
3247 } else {
3248 reducing.T = Reducing->Tr(mole_fractions);
3249 reducing.rhomolar = Reducing->rhormolar(mole_fractions);
3250 }
3251 return reducing;
3252}
3254 if (get_mole_fractions_ref().empty()) {
3255 throw ValueError("Mole fractions must be set before calling calc_reducing_state");
3256 }
3259 _crit = _reducing;
3260}
3261void HelmholtzEOSMixtureBackend::calc_all_alphar_deriv_cache(const std::vector<CoolPropDbl>& mole_fractions, const CoolPropDbl& tau,
3262 const CoolPropDbl& delta) {
3263 deriv_counter++;
3264 bool cache_values = true;
3265 HelmholtzDerivatives derivs = residual_helmholtz->all(*this, get_mole_fractions_ref(), tau, delta, cache_values);
3266 _alphar = derivs.alphar;
3267 _dalphar_dDelta = derivs.dalphar_ddelta;
3268 _dalphar_dTau = derivs.dalphar_dtau;
3269 _d2alphar_dDelta2 = derivs.d2alphar_ddelta2;
3270 _d2alphar_dDelta_dTau = derivs.d2alphar_ddelta_dtau;
3271 _d2alphar_dTau2 = derivs.d2alphar_dtau2;
3272 _d3alphar_dDelta3 = derivs.d3alphar_ddelta3;
3273 _d3alphar_dDelta2_dTau = derivs.d3alphar_ddelta2_dtau;
3274 _d3alphar_dDelta_dTau2 = derivs.d3alphar_ddelta_dtau2;
3275 _d3alphar_dTau3 = derivs.d3alphar_dtau3;
3276 _d4alphar_dDelta4 = derivs.d4alphar_ddelta4;
3277 _d4alphar_dDelta3_dTau = derivs.d4alphar_ddelta3_dtau;
3278 _d4alphar_dDelta2_dTau2 = derivs.d4alphar_ddelta2_dtau2;
3279 _d4alphar_dDelta_dTau3 = derivs.d4alphar_ddelta_dtau3;
3280 _d4alphar_dTau4 = derivs.d4alphar_dtau4;
3281}
3282
3283CoolPropDbl HelmholtzEOSMixtureBackend::calc_alphar_deriv_nocache(const int nTau, const int nDelta, const std::vector<CoolPropDbl>& mole_fractions,
3284 const CoolPropDbl& tau, const CoolPropDbl& delta) {
3285 bool cache_values = false;
3286 HelmholtzDerivatives derivs = residual_helmholtz->all(*this, mole_fractions, tau, delta, cache_values);
3287 return derivs.get(nTau, nDelta);
3288}
3289CoolPropDbl HelmholtzEOSMixtureBackend::calc_alpha0_deriv_nocache(const int nTau, const int nDelta, const std::vector<CoolPropDbl>& mole_fractions,
3290 const CoolPropDbl& tau, const CoolPropDbl& delta, const CoolPropDbl& Tr,
3291 const CoolPropDbl& rhor) {
3292 CoolPropDbl val;
3293 if (components.size() == 0) {
3294 throw ValueError("No alpha0 derivatives are available");
3295 }
3297 EquationOfState& E = components[0].EOS();
3298 // In the case of cubics, we need to use the shifted tau^*=Tc/T and delta^*=rho/rhoc
3299 // rather than tau=Tr/T and delta=rho/rhor
3300 // For multiparameter EOS, this changes nothing because Tc/Tr = 1 and rhoc/rhor = 1
3302
3303 // Cache the reducing temperature in some terms that need it (GERG-2004 models)
3304 E.alpha0.set_Tred(Tc);
3305 double taustar = Tc / Tr * tau, deltastar = rhor / rhomolarc * delta;
3306 if (nTau == 0 && nDelta == 0) {
3307 val = E.base0(taustar, deltastar);
3308 } else if (nTau == 0 && nDelta == 1) {
3309 val = E.dalpha0_dDelta(taustar, deltastar);
3310 } else if (nTau == 1 && nDelta == 0) {
3311 val = E.dalpha0_dTau(taustar, deltastar);
3312 } else if (nTau == 0 && nDelta == 2) {
3313 val = E.d2alpha0_dDelta2(taustar, deltastar);
3314 } else if (nTau == 1 && nDelta == 1) {
3315 val = E.d2alpha0_dDelta_dTau(taustar, deltastar);
3316 } else if (nTau == 2 && nDelta == 0) {
3317 val = E.d2alpha0_dTau2(taustar, deltastar);
3318 } else if (nTau == 0 && nDelta == 3) {
3319 val = E.d3alpha0_dDelta3(taustar, deltastar);
3320 } else if (nTau == 1 && nDelta == 2) {
3321 val = E.d3alpha0_dDelta2_dTau(taustar, deltastar);
3322 } else if (nTau == 2 && nDelta == 1) {
3323 val = E.d3alpha0_dDelta_dTau2(taustar, deltastar);
3324 } else if (nTau == 3 && nDelta == 0) {
3325 val = E.d3alpha0_dTau3(taustar, deltastar);
3326 } else {
3327 throw ValueError();
3328 }
3329 val *= pow(rhor / rhomolarc, nDelta);
3330 val /= pow(Tr / Tc, nTau);
3331 if (!ValidNumber(val)) {
3332 //calc_alpha0_deriv_nocache(nTau,nDelta,mole_fractions,tau,delta,Tr,rhor);
3333 throw ValueError(format("calc_alpha0_deriv_nocache returned invalid number with inputs nTau: %d, nDelta: %d, tau: %Lg, delta: %Lg", nTau,
3334 nDelta, tau, delta));
3335 } else {
3336 return val;
3337 }
3338 } else {
3339 // See Table B5, GERG 2008 from Kunz Wagner, JCED, 2012
3340 std::size_t N = mole_fractions.size();
3341 CoolPropDbl summer = 0;
3342 CoolPropDbl tau_i, delta_i, rho_ci, T_ci;
3343 CoolPropDbl Rmix = gas_constant();
3344 for (unsigned int i = 0; i < N; ++i) {
3345
3349 tau_i = T_ci * tau / Tr;
3350 delta_i = delta * rhor / rho_ci;
3351 CoolPropDbl Rratio = Rcomponent / Rmix;
3352
3353 // Cache the reducing temperature in some terms that need it (GERG-2004 models)
3354 components[i].EOS().alpha0.set_Tred(Tr);
3355
3356 if (nTau == 0 && nDelta == 0) {
3357 double logxi = (std::abs(mole_fractions[i]) > DBL_EPSILON) ? log(mole_fractions[i]) : 0;
3358 summer += mole_fractions[i] * Rratio * (components[i].EOS().base0(tau_i, delta_i) + logxi);
3359 } else if (nTau == 0 && nDelta == 1) {
3360 summer += mole_fractions[i] * Rratio * rhor / rho_ci * components[i].EOS().dalpha0_dDelta(tau_i, delta_i);
3361 } else if (nTau == 1 && nDelta == 0) {
3362 summer += mole_fractions[i] * Rratio * T_ci / Tr * components[i].EOS().dalpha0_dTau(tau_i, delta_i);
3363 } else if (nTau == 0 && nDelta == 2) {
3364 summer += mole_fractions[i] * Rratio * pow(rhor / rho_ci, 2) * components[i].EOS().d2alpha0_dDelta2(tau_i, delta_i);
3365 } else if (nTau == 1 && nDelta == 1) {
3366 summer += mole_fractions[i] * Rratio * rhor / rho_ci * T_ci / Tr * components[i].EOS().d2alpha0_dDelta_dTau(tau_i, delta_i);
3367 } else if (nTau == 2 && nDelta == 0) {
3368 summer += mole_fractions[i] * Rratio * pow(T_ci / Tr, 2) * components[i].EOS().d2alpha0_dTau2(tau_i, delta_i);
3369 } else {
3370 throw ValueError();
3371 }
3372 }
3373 return summer;
3374 }
3375}
3378 return static_cast<CoolPropDbl>(_alphar);
3379}
3382 return static_cast<CoolPropDbl>(_dalphar_dDelta);
3383}
3386 return static_cast<CoolPropDbl>(_dalphar_dTau);
3387}
3390 return static_cast<CoolPropDbl>(_d2alphar_dTau2);
3391}
3394 return static_cast<CoolPropDbl>(_d2alphar_dDelta_dTau);
3395}
3398 return static_cast<CoolPropDbl>(_d2alphar_dDelta2);
3399}
3402 return static_cast<CoolPropDbl>(_d3alphar_dDelta3);
3403}
3406 return static_cast<CoolPropDbl>(_d3alphar_dDelta2_dTau);
3407}
3410 return static_cast<CoolPropDbl>(_d3alphar_dDelta_dTau2);
3411}
3414 return static_cast<CoolPropDbl>(_d3alphar_dTau3);
3415}
3416
3419 return static_cast<CoolPropDbl>(_d4alphar_dDelta4);
3420}
3423 return static_cast<CoolPropDbl>(_d4alphar_dDelta3_dTau);
3424}
3427 return static_cast<CoolPropDbl>(_d4alphar_dDelta2_dTau2);
3428}
3431 return static_cast<CoolPropDbl>(_d4alphar_dDelta_dTau3);
3432}
3435 return static_cast<CoolPropDbl>(_d4alphar_dTau4);
3436}
3437
3439 const int nTau = 0, nDelta = 0;
3441}
3443 const int nTau = 0, nDelta = 1;
3445}
3447 const int nTau = 1, nDelta = 0;
3449}
3451 const int nTau = 0, nDelta = 2;
3453}
3455 const int nTau = 1, nDelta = 1;
3457}
3459 const int nTau = 2, nDelta = 0;
3461}
3463 const int nTau = 0, nDelta = 3;
3465}
3467 const int nTau = 1, nDelta = 2;
3469}
3471 const int nTau = 2, nDelta = 1;
3473}
3475 const int nTau = 3, nDelta = 0;
3477}
3480 // Derivative of temperature w.r.t. pressure ALONG the saturation curve
3481 CoolPropDbl dTdP_sat = T() * (1 / SatV.rhomolar() - 1 / SatL.rhomolar()) / (SatV.hmolar() - SatL.hmolar());
3482
3483 // "Trivial" inputs
3484 if (Of1 == iT && Wrt1 == iP) {
3485 return dTdP_sat;
3486 } else if (Of1 == iP && Wrt1 == iT) {
3487 return 1 / dTdP_sat;
3488 }
3489 // Derivative taken with respect to T
3490 else if (Wrt1 == iT) {
3491 return first_partial_deriv(Of1, iT, iP) + first_partial_deriv(Of1, iP, iT) / dTdP_sat;
3492 }
3493 // Derivative taken with respect to p
3494 else if (Wrt1 == iP) {
3495 return first_partial_deriv(Of1, iP, iT) + first_partial_deriv(Of1, iT, iP) * dTdP_sat;
3496 } else {
3497 throw ValueError(
3498 format("Not possible to take first saturation derivative with respect to %s", get_parameter_information(Wrt1, "short").c_str()));
3499 }
3500}
3502 if (!this->SatL || !this->SatV) throw ValueError(format("The saturation properties are needed for calc_first_saturation_deriv"));
3503
3504 // Derivative of temperature w.r.t. pressure ALONG the saturation curve
3505 CoolPropDbl dTdP_sat = T() * (1 / SatV->rhomolar() - 1 / SatL->rhomolar()) / (SatV->hmolar() - SatL->hmolar());
3506
3507 // "Trivial" inputs
3508 if (Of1 == iT && Wrt1 == iP) {
3509 return dTdP_sat;
3510 } else if (Of1 == iP && Wrt1 == iT) {
3511 return 1 / dTdP_sat;
3512 }
3513 // Derivative taken with respect to T
3514 else if (Wrt1 == iT) {
3515 return first_partial_deriv(Of1, iT, iP) + first_partial_deriv(Of1, iP, iT) / dTdP_sat;
3516 }
3517 // Derivative taken with respect to p
3518 else if (Wrt1 == iP) {
3519 return first_partial_deriv(Of1, iP, iT) + first_partial_deriv(Of1, iT, iP) * dTdP_sat;
3520 } else {
3521 throw ValueError(
3522 format("Not possible to take first saturation derivative with respect to %s", get_parameter_information(Wrt1, "short").c_str()));
3523 }
3524}
3526 if (!this->SatL || !this->SatV) throw ValueError(format("The saturation properties are needed for calc_second_saturation_deriv"));
3527 if (Wrt1 == iP && Wrt2 == iP) {
3528 CoolPropDbl dydT_constp = this->first_partial_deriv(Of1, iT, iP);
3529 CoolPropDbl d2ydTdp = this->second_partial_deriv(Of1, iT, iP, iP, iT);
3530 CoolPropDbl d2ydp2_constT = this->second_partial_deriv(Of1, iP, iT, iP, iT);
3531 CoolPropDbl d2ydT2_constp = this->second_partial_deriv(Of1, iT, iP, iT, iP);
3532
3533 CoolPropDbl dTdp_along_sat = calc_first_saturation_deriv(iT, iP);
3534 CoolPropDbl dvdrhoL = -1 / POW2(SatL->rhomolar());
3535 CoolPropDbl dvdrhoV = -1 / POW2(SatV->rhomolar());
3536 CoolPropDbl DELTAv = 1 / SatV->rhomolar() - 1 / SatL->rhomolar();
3537 CoolPropDbl dDELTAv_dT_constp = dvdrhoV * SatV->first_partial_deriv(iDmolar, iT, iP) - dvdrhoL * SatL->first_partial_deriv(iDmolar, iT, iP);
3538 CoolPropDbl dDELTAv_dp_constT = dvdrhoV * SatV->first_partial_deriv(iDmolar, iP, iT) - dvdrhoL * SatL->first_partial_deriv(iDmolar, iP, iT);
3539 CoolPropDbl DELTAh = SatV->hmolar() - SatL->hmolar();
3540 CoolPropDbl dDELTAh_dT_constp = SatV->first_partial_deriv(iHmolar, iT, iP) - SatL->first_partial_deriv(iHmolar, iT, iP);
3541 CoolPropDbl dDELTAh_dp_constT = SatV->first_partial_deriv(iHmolar, iP, iT) - SatL->first_partial_deriv(iHmolar, iP, iT);
3542 CoolPropDbl ddT_dTdp_along_sat_constp = (DELTAh * (_T * dDELTAv_dT_constp + DELTAv) - _T * DELTAv * dDELTAh_dT_constp) / POW2(DELTAh);
3543 CoolPropDbl ddp_dTdp_along_sat_constT = (DELTAh * (_T * dDELTAv_dp_constT) - _T * DELTAv * dDELTAh_dp_constT) / POW2(DELTAh);
3544
3545 double ddp_dydpsigma = d2ydp2_constT + dydT_constp * ddp_dTdp_along_sat_constT + d2ydTdp * dTdp_along_sat;
3546 double ddT_dydpsigma = d2ydTdp + dydT_constp * ddT_dTdp_along_sat_constp + d2ydT2_constp * dTdp_along_sat;
3547 return ddp_dydpsigma + ddT_dydpsigma * dTdp_along_sat;
3548 } else {
3549 throw ValueError(format("Currently, only possible to take second saturation derivative w.r.t. P (both times)"));
3550 }
3551}
3552
3554 parameters Constant2) {
3555 if (!this->SatL || !this->SatV) throw ValueError(format("The saturation properties are needed for calc_second_two_phase_deriv"));
3556
3557 if (Of == iDmolar
3558 && ((Wrt1 == iHmolar && Constant1 == iP && Wrt2 == iP && Constant2 == iHmolar)
3559 || (Wrt2 == iHmolar && Constant2 == iP && Wrt1 == iP && Constant1 == iHmolar))) {
3560 parameters h_key = iHmolar, rho_key = iDmolar, p_key = iP;
3561 // taking the derivative of (drho/dv)*(dv/dh|p) with respect to p with h constant
3562 CoolPropDbl dv_dh_constp = calc_first_two_phase_deriv(rho_key, h_key, p_key) / (-POW2(rhomolar()));
3563 CoolPropDbl drhomolar_dp__consth = first_two_phase_deriv(rho_key, p_key, h_key);
3564
3565 // Calculate the derivative of dvdh|p with respect to p at constant h
3566 CoolPropDbl dhL_dp_sat = SatL->calc_first_saturation_deriv(h_key, p_key, *SatL, *SatV);
3567 CoolPropDbl dhV_dp_sat = SatV->calc_first_saturation_deriv(h_key, p_key, *SatL, *SatV);
3568 CoolPropDbl drhoL_dp_sat = SatL->calc_first_saturation_deriv(rho_key, p_key, *SatL, *SatV);
3569 CoolPropDbl drhoV_dp_sat = SatV->calc_first_saturation_deriv(rho_key, p_key, *SatL, *SatV);
3570 CoolPropDbl numerator = 1 / SatV->keyed_output(rho_key) - 1 / SatL->keyed_output(rho_key);
3571 CoolPropDbl denominator = SatV->keyed_output(h_key) - SatL->keyed_output(h_key);
3572 CoolPropDbl dnumerator = -1 / POW2(SatV->keyed_output(rho_key)) * drhoV_dp_sat + 1 / POW2(SatL->keyed_output(rho_key)) * drhoL_dp_sat;
3573 CoolPropDbl ddenominator = dhV_dp_sat - dhL_dp_sat;
3574 CoolPropDbl d_dvdh_dp__consth = (denominator * dnumerator - numerator * ddenominator) / POW2(denominator);
3575 return -POW2(rhomolar()) * d_dvdh_dp__consth + dv_dh_constp * (-2 * rhomolar()) * drhomolar_dp__consth;
3576 } else if (Of == iDmass
3577 && ((Wrt1 == iHmass && Constant1 == iP && Wrt2 == iP && Constant2 == iHmass)
3578 || (Wrt2 == iHmass && Constant2 == iP && Wrt1 == iP && Constant1 == iHmass))) {
3579 parameters h_key = iHmass, rho_key = iDmass, p_key = iP;
3580 CoolPropDbl rho = keyed_output(rho_key);
3581 // taking the derivative of (drho/dv)*(dv/dh|p) with respect to p with h constant
3582 CoolPropDbl dv_dh_constp = calc_first_two_phase_deriv(rho_key, h_key, p_key) / (-POW2(rho));
3583 CoolPropDbl drho_dp__consth = first_two_phase_deriv(rho_key, p_key, h_key);
3584
3585 // Calculate the derivative of dvdh|p with respect to p at constant h
3586 CoolPropDbl dhL_dp_sat = SatL->calc_first_saturation_deriv(h_key, p_key, *SatL, *SatV);
3587 CoolPropDbl dhV_dp_sat = SatV->calc_first_saturation_deriv(h_key, p_key, *SatL, *SatV);
3588 CoolPropDbl drhoL_dp_sat = SatL->calc_first_saturation_deriv(rho_key, p_key, *SatL, *SatV);
3589 CoolPropDbl drhoV_dp_sat = SatV->calc_first_saturation_deriv(rho_key, p_key, *SatL, *SatV);
3590 CoolPropDbl numerator = 1 / SatV->keyed_output(rho_key) - 1 / SatL->keyed_output(rho_key);
3591 CoolPropDbl denominator = SatV->keyed_output(h_key) - SatL->keyed_output(h_key);
3592 CoolPropDbl dnumerator = -1 / POW2(SatV->keyed_output(rho_key)) * drhoV_dp_sat + 1 / POW2(SatL->keyed_output(rho_key)) * drhoL_dp_sat;
3593 CoolPropDbl ddenominator = dhV_dp_sat - dhL_dp_sat;
3594 CoolPropDbl d_dvdh_dp__consth = (denominator * dnumerator - numerator * ddenominator) / POW2(denominator);
3595 return -POW2(rho) * d_dvdh_dp__consth + dv_dh_constp * (-2 * rho) * drho_dp__consth;
3596 } else {
3597 throw ValueError();
3598 }
3599}
3601 if (!this->SatL || !this->SatV) throw ValueError(format("The saturation properties are needed for calc_first_two_phase_deriv"));
3602 if (Of == iDmolar && Wrt == iHmolar && Constant == iP) {
3603 return -POW2(rhomolar()) * (1 / SatV->rhomolar() - 1 / SatL->rhomolar()) / (SatV->hmolar() - SatL->hmolar());
3604 } else if (Of == iDmass && Wrt == iHmass && Constant == iP) {
3605 return -POW2(rhomass()) * (1 / SatV->rhomass() - 1 / SatL->rhomass()) / (SatV->hmass() - SatL->hmass());
3606 } else if (Of == iDmolar && Wrt == iP && Constant == iHmolar) {
3607 // v = 1/rho; drhodv = -rho^2; dvdrho = -1/rho^2
3608 CoolPropDbl dvdrhoL = -1 / POW2(SatL->rhomolar());
3609 CoolPropDbl dvdrhoV = -1 / POW2(SatV->rhomolar());
3610 CoolPropDbl dvL_dp = dvdrhoL * SatL->calc_first_saturation_deriv(iDmolar, iP, *SatL, *SatV);
3611 CoolPropDbl dvV_dp = dvdrhoV * SatV->calc_first_saturation_deriv(iDmolar, iP, *SatL, *SatV);
3612 CoolPropDbl dhL_dp = SatL->calc_first_saturation_deriv(iHmolar, iP, *SatL, *SatV);
3613 CoolPropDbl dhV_dp = SatV->calc_first_saturation_deriv(iHmolar, iP, *SatL, *SatV);
3614 CoolPropDbl dxdp_h = (Q() * dhV_dp + (1 - Q()) * dhL_dp) / (SatL->hmolar() - SatV->hmolar());
3615 CoolPropDbl dvdp_h = dvL_dp + dxdp_h * (1 / SatV->rhomolar() - 1 / SatL->rhomolar()) + Q() * (dvV_dp - dvL_dp);
3616 return -POW2(rhomolar()) * dvdp_h;
3617 } else if (Of == iDmass && Wrt == iP && Constant == iHmass) {
3618 // v = 1/rho; drhodv = -rho^2; dvdrho = -1/rho^2
3619 CoolPropDbl dvdrhoL = -1 / POW2(SatL->rhomass());
3620 CoolPropDbl dvdrhoV = -1 / POW2(SatV->rhomass());
3621 CoolPropDbl dvL_dp = dvdrhoL * SatL->calc_first_saturation_deriv(iDmass, iP, *SatL, *SatV);
3622 CoolPropDbl dvV_dp = dvdrhoV * SatV->calc_first_saturation_deriv(iDmass, iP, *SatL, *SatV);
3623 CoolPropDbl dhL_dp = SatL->calc_first_saturation_deriv(iHmass, iP, *SatL, *SatV);
3624 CoolPropDbl dhV_dp = SatV->calc_first_saturation_deriv(iHmass, iP, *SatL, *SatV);
3625 CoolPropDbl dxdp_h = (Q() * dhV_dp + (1 - Q()) * dhL_dp) / (SatL->hmass() - SatV->hmass());
3626 CoolPropDbl dvdp_h = dvL_dp + dxdp_h * (1 / SatV->rhomass() - 1 / SatL->rhomass()) + Q() * (dvV_dp - dvL_dp);
3627 return -POW2(rhomass()) * dvdp_h;
3628 } else {
3629 throw ValueError("These inputs are not supported to calc_first_two_phase_deriv");
3630 }
3631}
3633 // Note: If you need all three values (drho_dh__p, drho_dp__h and rho_spline),
3634 // you should calculate drho_dp__h first to avoid duplicate calculations.
3635
3636 bool drho_dh__p = false;
3637 bool drho_dp__h = false;
3638 bool rho_spline = false;
3639
3640 if (Of == iDmolar && Wrt == iHmolar && Constant == iP) {
3641 drho_dh__p = true;
3643 } else if (Of == iDmass && Wrt == iHmass && Constant == iP) {
3645 } else if (Of == iDmolar && Wrt == iP && Constant == iHmolar) {
3646 drho_dp__h = true;
3648 } else if (Of == iDmass && Wrt == iP && Constant == iHmass) {
3650 }
3651 // Add the special case for the splined density
3652 else if (Of == iDmolar && Wrt == iDmolar && Constant == iDmolar) {
3653 rho_spline = true;
3654 if (_rho_spline) return _rho_spline;
3655 } else if (Of == iDmass && Wrt == iDmass && Constant == iDmass) {
3657 } else {
3658 throw ValueError("These inputs are not supported to calc_first_two_phase_deriv");
3659 }
3660
3661 if (!this->SatL || !this->SatV) throw ValueError(format("The saturation properties are needed for calc_first_two_phase_deriv_splined"));
3662 if (_Q > x_end) {
3663 throw ValueError(format("Q [%g] is greater than x_end [%Lg]", _Q, x_end).c_str());
3664 }
3665 if (_phase != iphase_twophase) {
3666 throw ValueError(format("state is not two-phase"));
3667 }
3668
3669 shared_ptr<HelmholtzEOSMixtureBackend> Liq(new HelmholtzEOSMixtureBackend(this->get_components())),
3671
3672 Liq->specify_phase(iphase_liquid);
3673 Liq->_Q = -1;
3674 Liq->update_DmolarT_direct(SatL->rhomolar(), SatL->T());
3675 End->update(QT_INPUTS, x_end, SatL->T());
3676
3677 CoolPropDbl Delta = Q() * (SatV->keyed_output(iHmolar) - SatL->keyed_output(iHmolar));
3678 CoolPropDbl Delta_end = End->keyed_output(iHmolar) - SatL->keyed_output(iHmolar);
3679
3680 // At the end of the zone to which spline is applied
3681 CoolPropDbl drho_dh_end = End->calc_first_two_phase_deriv(iDmolar, iHmolar, iP);
3682 CoolPropDbl rho_end = End->keyed_output(iDmolar);
3683
3684 // Faking single-phase
3685 CoolPropDbl rho_liq = Liq->keyed_output(iDmolar);
3686 CoolPropDbl drho_dh_liq__constp = Liq->first_partial_deriv(iDmolar, iHmolar, iP);
3687
3688 // Spline coordinates a, b, c, d
3689 CoolPropDbl Abracket = (2 * rho_liq - 2 * rho_end + Delta_end * (drho_dh_liq__constp + drho_dh_end));
3690 CoolPropDbl a = 1 / POW3(Delta_end) * Abracket;
3691 CoolPropDbl b = 3 / POW2(Delta_end) * (-rho_liq + rho_end) - 1 / Delta_end * (drho_dh_end + 2 * drho_dh_liq__constp);
3692 CoolPropDbl c = drho_dh_liq__constp;
3693 CoolPropDbl d = rho_liq;
3694
3695 // Either the spline value or drho/dh|p can be directly evaluated now
3696 _rho_spline = a * POW3(Delta) + b * POW2(Delta) + c * Delta + d;
3697 _drho_spline_dh__constp = 3 * a * POW2(Delta) + 2 * b * Delta + c;
3698 if (rho_spline) return _rho_spline;
3699 if (drho_dh__p) return _drho_spline_dh__constp;
3700
3701 // It's drho/dp|h
3702 // ... calculate some more things
3703
3704 // Derivatives *along* the saturation curve using the special internal method
3705 CoolPropDbl dhL_dp_sat = SatL->calc_first_saturation_deriv(iHmolar, iP, *SatL, *SatV);
3706 CoolPropDbl dhV_dp_sat = SatV->calc_first_saturation_deriv(iHmolar, iP, *SatL, *SatV);
3707 CoolPropDbl drhoL_dp_sat = SatL->calc_first_saturation_deriv(iDmolar, iP, *SatL, *SatV);
3708 CoolPropDbl drhoV_dp_sat = SatV->calc_first_saturation_deriv(iDmolar, iP, *SatL, *SatV);
3709 CoolPropDbl rhoV = SatV->keyed_output(iDmolar);
3710 CoolPropDbl rhoL = SatL->keyed_output(iDmolar);
3711 CoolPropDbl drho_dp_end = POW2(End->keyed_output(iDmolar)) * (x_end / POW2(rhoV) * drhoV_dp_sat + (1 - x_end) / POW2(rhoL) * drhoL_dp_sat);
3712
3713 // Faking single-phase
3714 //CoolPropDbl drho_dp__consth_liq = Liq->first_partial_deriv(iDmolar, iP, iHmolar);
3715 CoolPropDbl d2rhodhdp_liq = Liq->second_partial_deriv(iDmolar, iHmolar, iP, iP, iHmolar); // ?
3716
3717 // Derivatives at the end point
3718 // CoolPropDbl drho_dp__consth_end = End->calc_first_two_phase_deriv(iDmolar, iP, iHmolar);
3719 CoolPropDbl d2rhodhdp_end = End->calc_second_two_phase_deriv(iDmolar, iHmolar, iP, iP, iHmolar);
3720
3721 // Reminder:
3722 // Delta = Q()*(hV-hL) = h-hL
3723 // Delta_end = x_end*(hV-hL);
3724 CoolPropDbl d_Delta_dp__consth = -dhL_dp_sat;
3725 CoolPropDbl d_Delta_end_dp__consth = x_end * (dhV_dp_sat - dhL_dp_sat);
3726
3727 // First pressure derivative at constant h of the coefficients a,b,c,d
3728 // CoolPropDbl Abracket = (2*rho_liq - 2*rho_end + Delta_end * (drho_dh_liq__constp + drho_dh_end));
3729 CoolPropDbl d_Abracket_dp_consth = (2 * drhoL_dp_sat - 2 * drho_dp_end + Delta_end * (d2rhodhdp_liq + d2rhodhdp_end)
3730 + d_Delta_end_dp__consth * (drho_dh_liq__constp + drho_dh_end));
3731 CoolPropDbl da_dp = 1 / POW3(Delta_end) * d_Abracket_dp_consth + Abracket * (-3 / POW4(Delta_end) * d_Delta_end_dp__consth);
3732 CoolPropDbl db_dp = -6 / POW3(Delta_end) * d_Delta_end_dp__consth * (rho_end - rho_liq) + 3 / POW2(Delta_end) * (drho_dp_end - drhoL_dp_sat)
3733 + (1 / POW2(Delta_end) * d_Delta_end_dp__consth) * (drho_dh_end + 2 * drho_dh_liq__constp)
3734 - (1 / Delta_end) * (d2rhodhdp_end + 2 * d2rhodhdp_liq);
3735 CoolPropDbl dc_dp = d2rhodhdp_liq;
3736 CoolPropDbl dd_dp = drhoL_dp_sat;
3737
3739 (3 * a * POW2(Delta) + 2 * b * Delta + c) * d_Delta_dp__consth + POW3(Delta) * da_dp + POW2(Delta) * db_dp + Delta * dc_dp + dd_dp;
3740 if (drho_dp__h) return _drho_spline_dp__consth;
3741
3742 throw ValueError("Something went wrong in HelmholtzEOSMixtureBackend::calc_first_two_phase_deriv_splined");
3743 return _HUGE;
3744}
3745
3747 class Resid : public FuncWrapperND
3748 {
3749 public:
3751 double L1, M1;
3752 Eigen::MatrixXd Lstar, Mstar;
3753 Resid(HelmholtzEOSMixtureBackend& HEOS) : HEOS(HEOS), L1(_HUGE), M1(_HUGE){};
3754 std::vector<double> call(const std::vector<double>& tau_delta) {
3755 double rhomolar = tau_delta[1] * HEOS.rhomolar_reducing();
3756 double T = HEOS.T_reducing() / tau_delta[0];
3759 Mstar = MixtureDerivatives::Mstar(HEOS, XN_INDEPENDENT, Lstar);
3760 std::vector<double> o(2);
3761 o[0] = Lstar.determinant();
3762 o[1] = Mstar.determinant();
3763 return o;
3764 };
3765 std::vector<std::vector<double>> Jacobian(const std::vector<double>& x) {
3766 std::size_t N = x.size();
3767 std::vector<std::vector<double>> J(N, std::vector<double>(N, 0));
3768 Eigen::MatrixXd adjL = adjugate(Lstar), adjM = adjugate(Mstar), dLdTau = MixtureDerivatives::dLstar_dX(HEOS, XN_INDEPENDENT, iTau),
3770 dMdTau = MixtureDerivatives::dMstar_dX(HEOS, XN_INDEPENDENT, iTau, Lstar, dLdTau),
3771 dMdDelta = MixtureDerivatives::dMstar_dX(HEOS, XN_INDEPENDENT, iDelta, Lstar, dLdDelta);
3772
3773 J[0][0] = (adjL * dLdTau).trace();
3774 J[0][1] = (adjL * dLdDelta).trace();
3775 J[1][0] = (adjM * dMdTau).trace();
3776 J[1][1] = (adjM * dMdDelta).trace();
3777 return J;
3778 }
3780 std::vector<std::vector<double>> numerical_Jacobian(const std::vector<double>& x) {
3781 std::size_t N = x.size();
3782 std::vector<double> rplus, rminus, xp;
3783 std::vector<std::vector<double>> J(N, std::vector<double>(N, 0));
3784
3785 double epsilon = 0.0001;
3786
3787 // Build the Jacobian by column
3788 for (std::size_t i = 0; i < N; ++i) {
3789 xp = x;
3790 xp[i] += epsilon;
3791 rplus = call(xp);
3792 xp[i] -= 2 * epsilon;
3793 rminus = call(xp);
3794
3795 for (std::size_t j = 0; j < N; ++j) {
3796 J[j][i] = (rplus[j] - rminus[j]) / (2 * epsilon);
3797 }
3798 }
3799 std::cout << J[0][0] << " " << J[0][1] << std::endl;
3800 std::cout << J[1][0] << " " << J[1][1] << std::endl;
3801 return J;
3802 };
3803 };
3804 Resid resid(*this);
3805 std::vector<double> x, tau_delta(2);
3806 tau_delta[0] = T_reducing() / T0;
3807 tau_delta[1] = rho0 / rhomolar_reducing();
3808 x = NDNewtonRaphson_Jacobian(&resid, tau_delta, 1e-10, 100);
3809 _critical.T = T_reducing() / x[0];
3812
3813 CriticalState critical;
3814 critical.T = _critical.T;
3815 critical.p = _critical.p;
3816 critical.rhomolar = _critical.rhomolar;
3817 if (_critical.p < 0) {
3818 critical.stable = false;
3819 } else {
3820 if (get_config_bool(ASSUME_CRITICAL_POINT_STABLE)) {
3821 critical.stable = true;
3822 } else {
3823 // Otherwise we try to check stability with TPD-based analysis
3824 StabilityRoutines::StabilityEvaluationClass stability_tester(*this);
3825 critical.stable = stability_tester.is_stable();
3826 }
3827 }
3828 return critical;
3829}
3830
3835{
3836 public:
3838 const double delta;
3840 OneDimObjective(HelmholtzEOSMixtureBackend& HEOS, double delta0) : HEOS(HEOS), delta(delta0), _call(_HUGE), _deriv(_HUGE), _second_deriv(_HUGE){};
3841 double call(double tau) {
3842 double rhomolar = HEOS.rhomolar_reducing() * delta, T = HEOS.T_reducing() / tau;
3843 HEOS.update_DmolarT_direct(rhomolar, T);
3845 return _call;
3846 }
3847 double deriv(double tau) {
3848 Eigen::MatrixXd adjL = adjugate(MixtureDerivatives::Lstar(HEOS, XN_INDEPENDENT)),
3850 _deriv = (adjL * dLdTau).trace();
3851 return _deriv;
3852 };
3853 double second_deriv(double tau) {
3854 Eigen::MatrixXd Lstar = MixtureDerivatives::Lstar(HEOS, XN_INDEPENDENT),
3856 d2LstardTau2 = MixtureDerivatives::d2Lstar_dX2(HEOS, XN_INDEPENDENT, iTau, iTau), adjL = adjugate(Lstar),
3857 dadjLstardTau = adjugate_derivative(Lstar, dLstardTau);
3858 _second_deriv = (dLstardTau * dadjLstardTau + adjL * d2LstardTau2).trace();
3859 return _second_deriv;
3860 };
3861};
3862
3866{
3867 public:
3869 double delta, tau,
3876 std::vector<CoolProp::CriticalState> critical_points;
3880 bool
3882 L0CurveTracer(HelmholtzEOSMixtureBackend& HEOS, double tau0, double delta0)
3883 : HEOS(HEOS), delta(delta0), tau(tau0), M1_last(_HUGE), N_critical_points(0), find_critical_points(true) {
3884 R_delta_tracer = 0.1;
3886 R_tau_tracer = 0.1;
3888 };
3889 /***
3890 \brief Update values for tau, delta
3891 @param theta The angle
3892 @param tau The old value of tau
3893 @param delta The old value of delta
3894 @param tau_new The new value of tau
3895 @param delta_new The new value of delta
3896 */
3897 void get_tau_delta(const double theta, const double tau, const double delta, double& tau_new, double& delta_new) {
3898 tau_new = tau + R_tau * cos(theta);
3899 delta_new = delta + R_delta * sin(theta);
3900 };
3901 /***
3902 \brief Calculate the value of L1
3903 @param theta The angle
3904 */
3905 double call(double theta) {
3906 double tau_new, delta_new;
3907 this->get_tau_delta(theta, tau, delta, tau_new, delta_new);
3908 double rhomolar = HEOS.rhomolar_reducing() * delta_new, T = HEOS.T_reducing() / tau_new;
3909 HEOS.update_DmolarT_direct(rhomolar, T);
3911 adjLstar = adjugate(Lstar);
3914 double L1 = Lstar.determinant();
3915 return L1;
3916 };
3917 /***
3918 \brief Calculate the first partial derivative of L1 with respect to the angle
3919 @param theta The angle
3920 */
3921 double deriv(double theta) {
3922 double dL1_dtau = (adjLstar * dLstardTau).trace(), dL1_ddelta = (adjLstar * dLstardDelta).trace();
3923 return -R_tau * sin(theta) * dL1_dtau + R_delta * cos(theta) * dL1_ddelta;
3924 };
3925
3926 void trace() {
3927 bool debug = (get_debug_level() > 0) | false;
3928 double theta;
3929 for (int i = 0; i < 300; ++i) {
3930 if (i == 0) {
3931 // In the first iteration, search all angles in the positive delta direction using a
3932 // bounded solver with a very small radius in order to not hit other L1*=0 contours
3933 // that are in the vicinity
3934 R_tau = 0.001;
3935 R_delta = 0.001;
3936 theta = Brent(this, 0, M_PI, DBL_EPSILON, 1e-10, 100);
3937 } else {
3938 // In subsequent iterations, you already have an excellent guess for the direction to
3939 // be searching in, use Newton's method to refine the solution since we also
3940 // have an analytic solution for the derivative
3943 try {
3944 theta = Newton(this, theta_last, 1e-10, 100);
3945 } catch (...) {
3946 if (N_critical_points > 0 && delta > 1.5 * critical_points[0].rhomolar / HEOS.rhomolar_reducing()) {
3947 // Stopping the search - probably we have a kink in the L1*=0 contour
3948 // caused by poor low-temperature behavior
3949 continue;
3950 }
3951 }
3952
3953 // If the solver takes a U-turn, going in the opposite direction of travel
3954 // this is not a good thing, and force a Brent's method solver call to make sure we keep
3955 // tracing in the same direction
3956 if (std::abs(angle_difference(theta, theta_last)) > M_PI / 2.0) {
3957 // We have found at least one critical point and we are now well above the density
3958 // associated with the first critical point
3959 if (N_critical_points > 0 && delta > 1.2 * critical_points[0].rhomolar / HEOS.rhomolar_reducing()) {
3960 // Stopping the search - probably we have a kink in the L1*=0 contour
3961 // caused by poor low-temperature behavior
3962 continue;
3963 } else {
3964 theta = Brent(this, theta_last - M_PI / 3.5, theta_last + M_PI / 3.5, DBL_EPSILON, 1e-10, 100);
3965 }
3966 }
3967 }
3968
3969 // Calculate the second criticality condition
3970 double M1 = MixtureDerivatives::Mstar(HEOS, XN_INDEPENDENT, Lstar).determinant();
3971 double p_MPa = HEOS.p() / 1e6;
3972
3973 // Calculate the new tau and delta at the new point
3974 double tau_new, delta_new;
3975 this->get_tau_delta(theta, tau, delta, tau_new, delta_new);
3976
3977 // Stop if bounds are exceeded
3978 if (p_MPa > 500 || HEOS.get_critical_is_terminated(delta_new, tau_new)) {
3979 break;
3980 }
3981
3982 // If the sign of M1 and the previous value of M1 have different signs, it means that
3983 // you have bracketed a critical point, run the full critical point solver to
3984 // find the critical point and store it
3985 // Only enabled if find_critical_points is true (the default)
3986 if (i > 0 && M1 * M1_last < 0 && find_critical_points) {
3987 double rhomolar = HEOS.rhomolar_reducing() * (delta + delta_new) / 2.0, T = HEOS.T_reducing() / ((tau + tau_new) / 2.0);
3989 critical_points.push_back(crit);
3991 if (debug) {
3992 std::cout << HEOS.get_mole_fractions()[0] << " " << crit.rhomolar << " " << crit.T << " " << p_MPa << std::endl;
3993 }
3994 }
3995
3996 // Update the storage values
3997 this->tau = tau_new;
3998 this->delta = delta_new;
3999 this->M1_last = M1;
4000 this->theta_last = theta;
4001
4002 this->spinodal_values.tau.push_back(tau_new);
4003 this->spinodal_values.delta.push_back(delta_new);
4004 this->spinodal_values.M1.push_back(M1);
4005 }
4006 };
4007};
4008
4010 Eigen::MatrixXd Lstar = MixtureDerivatives::Lstar(*this, XN_INDEPENDENT);
4011 Eigen::MatrixXd Mstar = MixtureDerivatives::Mstar(*this, XN_INDEPENDENT, Lstar);
4012 L1star = Lstar.determinant();
4013 M1star = Mstar.determinant();
4014};
4015
4017 R_delta = 0.025;
4018 R_tau = 0.1;
4019}
4020std::vector<CoolProp::CriticalState> HelmholtzEOSMixtureBackend::_calc_all_critical_points(bool find_critical_points) {
4021 // Populate the temporary class used to calculate the critical point(s)
4023 if (get_debug_level() > 10) {
4024 rapidjson::Document doc;
4025 doc.SetObject();
4026 rapidjson::Value& val = doc;
4027 std::vector<std::vector<DepartureFunctionPointer>>& mat = critical_state->residual_helmholtz->Excess.DepartureFunctionMatrix;
4028 if (mat.size() > 0) {
4029 mat[0][1]->phi.to_json(val, doc);
4030 std::cout << cpjson::to_string(doc);
4031 }
4032 }
4033 critical_state->set_mole_fractions(this->get_mole_fractions_ref());
4034
4035 // Specify state to be something homogeneous to shortcut phase evaluation
4036 critical_state->specify_phase(iphase_gas);
4037
4038 double delta0 = _HUGE, tau0 = _HUGE;
4039 critical_state->get_critical_point_starting_values(delta0, tau0);
4040
4041 OneDimObjective resid_L0(*critical_state, delta0);
4042
4043 // If the derivative of L1star with respect to tau is positive,
4044 // tau needs to be increased such that we sit on the other
4045 // side of the d(L1star)/dtau = 0 contour
4046 resid_L0.call(tau0);
4047 int bump_count = 0;
4048 while (resid_L0.deriv(tau0) > 0 && bump_count < 3) {
4049 tau0 *= 1.1;
4050 bump_count++;
4051 }
4052 double tau_L0 = Halley(resid_L0, tau0, 1e-10, 100);
4053 //double T0 = T_reducing()/tau_L0;
4054 //double rho0 = delta0*rhomolar_reducing();
4055
4056 L0CurveTracer tracer(*critical_state, tau_L0, delta0);
4057 tracer.find_critical_points = find_critical_points;
4058
4059 double R_delta = 0, R_tau = 0;
4060 critical_state->get_critical_point_search_radii(R_delta, R_tau);
4061 tracer.R_delta_tracer = R_delta;
4062 tracer.R_tau_tracer = R_tau;
4063 tracer.trace();
4064
4065 this->spinodal_values = tracer.spinodal_values;
4066
4067 return tracer.critical_points;
4068}
4069
4070double HelmholtzEOSMixtureBackend::calc_tangent_plane_distance(const double T, const double p, const std::vector<double>& w,
4071 const double rhomolar_guess) {
4072
4073 const std::vector<CoolPropDbl>& z = this->get_mole_fractions_ref();
4074 if (w.size() != z.size()) {
4075 throw ValueError(format("Trial composition vector size [%d] is not the same as bulk composition [%d]", w.size(), z.size()));
4076 }
4077 add_TPD_state();
4078 TPD_state->set_mole_fractions(w);
4079
4080 CoolPropDbl rho = TPD_state->solver_rho_Tp_global(T, p, 0.9 / TPD_state->SRK_covolume());
4081 TPD_state->update_DmolarT_direct(rho, T);
4082
4083 double summer = 0;
4084 for (std::size_t i = 0; i < w.size(); ++i) {
4085 summer +=
4087 }
4088 return summer;
4089}
4090
4092 // Ok, we are faking a little bit here, hijacking the code for critical points, but skipping evaluation of critical points
4093 bool find_critical_points = false;
4094 _calc_all_critical_points(find_critical_points);
4095}
4096
4097void HelmholtzEOSMixtureBackend::set_reference_stateS(const std::string& reference_state) {
4098 for (std::size_t i = 0; i < components.size(); ++i) {
4099 CoolProp::HelmholtzEOSMixtureBackend HEOS(std::vector<CoolPropFluid>(1, components[i]));
4100 if (!reference_state.compare("IIR")) {
4101 if (HEOS.Ttriple() > 273.15) {
4102 throw ValueError(format("Cannot use IIR reference state; Ttriple [%Lg] is greater than 273.15 K", HEOS.Ttriple()));
4103 }
4104 HEOS.update(QT_INPUTS, 0, 273.15);
4105
4106 // Get current values for the enthalpy and entropy
4107 double deltah = HEOS.hmass() - 200000; // offset from 200000 J/kg enthalpy
4108 double deltas = HEOS.smass() - 1000; // offset from 1000 J/kg/K entropy
4109 double delta_a1 = deltas / (HEOS.gas_constant() / HEOS.molar_mass());
4110 double delta_a2 = -deltah / (HEOS.gas_constant() / HEOS.molar_mass() * HEOS.get_reducing_state().T);
4111 // Change the value in the library for the given fluid
4112 set_fluid_enthalpy_entropy_offset(components[i], delta_a1, delta_a2, "IIR");
4113 if (get_debug_level() > 0) {
4114 std::cout << format("set offsets to %0.15g and %0.15g\n", delta_a1, delta_a2);
4115 }
4116 } else if (!reference_state.compare("ASHRAE")) {
4117 if (HEOS.Ttriple() > 233.15) {
4118 throw ValueError(format("Cannot use ASHRAE reference state; Ttriple [%Lg] is greater than than 233.15 K", HEOS.Ttriple()));
4119 }
4120 HEOS.update(QT_INPUTS, 0, 233.15);
4121
4122 // Get current values for the enthalpy and entropy
4123 double deltah = HEOS.hmass() - 0; // offset from 0 J/kg enthalpy
4124 double deltas = HEOS.smass() - 0; // offset from 0 J/kg/K entropy
4125 double delta_a1 = deltas / (HEOS.gas_constant() / HEOS.molar_mass());
4126 double delta_a2 = -deltah / (HEOS.gas_constant() / HEOS.molar_mass() * HEOS.get_reducing_state().T);
4127 // Change the value in the library for the given fluid
4128 set_fluid_enthalpy_entropy_offset(components[i], delta_a1, delta_a2, "ASHRAE");
4129 if (get_debug_level() > 0) {
4130 std::cout << format("set offsets to %0.15g and %0.15g\n", delta_a1, delta_a2);
4131 }
4132 } else if (!reference_state.compare("NBP")) {
4133 if (HEOS.p_triple() > 101325) {
4134 throw ValueError(format("Cannot use NBP reference state; p_triple [%Lg Pa] is greater than than 101325 Pa", HEOS.p_triple()));
4135 }
4136 // Saturated liquid boiling point at 1 atmosphere
4137 HEOS.update(PQ_INPUTS, 101325, 0);
4138
4139 double deltah = HEOS.hmass() - 0; // offset from 0 kJ/kg enthalpy
4140 double deltas = HEOS.smass() - 0; // offset from 0 kJ/kg/K entropy
4141 double delta_a1 = deltas / (HEOS.gas_constant() / HEOS.molar_mass());
4142 double delta_a2 = -deltah / (HEOS.gas_constant() / HEOS.molar_mass() * HEOS.get_reducing_state().T);
4143 // Change the value in the library for the given fluid
4144 set_fluid_enthalpy_entropy_offset(components[i], delta_a1, delta_a2, "NBP");
4145 if (get_debug_level() > 0) {
4146 std::cout << format("set offsets to %0.15g and %0.15g\n", delta_a1, delta_a2);
4147 }
4148 } else if (!reference_state.compare("DEF")) {
4150 } else if (!reference_state.compare("RESET")) {
4152 } else {
4153 throw ValueError(format("reference state string is invalid: [%s]", reference_state.c_str()));
4154 }
4155 }
4156}
4157
4163void HelmholtzEOSMixtureBackend::set_reference_stateD(double T, double rhomolar, double hmolar0, double smolar0) {
4164 for (std::size_t i = 0; i < components.size(); ++i) {
4165 CoolProp::HelmholtzEOSMixtureBackend HEOS(std::vector<CoolPropFluid>(1, components[i]));
4166
4168
4169 // Get current values for the enthalpy and entropy
4170 double deltah = HEOS.hmolar() - hmolar0; // offset from specified enthalpy in J/mol
4171 double deltas = HEOS.smolar() - smolar0; // offset from specified entropy in J/mol/K
4172 double delta_a1 = deltas / (HEOS.gas_constant());
4173 double delta_a2 = -deltah / (HEOS.gas_constant() * HEOS.get_reducing_state().T);
4174 set_fluid_enthalpy_entropy_offset(components[i], delta_a1, delta_a2, "custom");
4175 }
4176}
4177
4179 const std::string& ref) {
4180 component.EOS().alpha0.EnthalpyEntropyOffset.set(delta_a1, delta_a2, ref);
4181
4182 shared_ptr<CoolProp::HelmholtzEOSBackend> HEOS(new CoolProp::HelmholtzEOSBackend(component));
4183 HEOS->specify_phase(iphase_gas); // Something homogeneous;
4184 // Calculate the new enthalpy and entropy values
4185 HEOS->update(DmolarT_INPUTS, component.EOS().hs_anchor.rhomolar, component.EOS().hs_anchor.T);
4186 component.EOS().hs_anchor.hmolar = HEOS->hmolar();
4187 component.EOS().hs_anchor.smolar = HEOS->smolar();
4188
4189 double f = (HEOS->name() == "Water" || HEOS->name() == "CarbonDioxide") ? 1.00001 : 1.0;
4190
4191 // Calculate the new enthalpy and entropy values at the reducing state
4192 HEOS->update(DmolarT_INPUTS, component.EOS().reduce.rhomolar * f, component.EOS().reduce.T * f);
4193 component.EOS().reduce.hmolar = HEOS->hmolar();
4194 component.EOS().reduce.smolar = HEOS->smolar();
4195
4196 // Calculate the new enthalpy and entropy values at the critical state
4197 HEOS->update(DmolarT_INPUTS, component.crit.rhomolar * f, component.crit.T * f);
4198 component.crit.hmolar = HEOS->hmolar();
4199 component.crit.smolar = HEOS->smolar();
4200
4201 // Calculate the new enthalpy and entropy values
4202 HEOS->update(DmolarT_INPUTS, component.triple_liquid.rhomolar, component.triple_liquid.T);
4203 component.triple_liquid.hmolar = HEOS->hmolar();
4204 component.triple_liquid.smolar = HEOS->smolar();
4205
4206 // Calculate the new enthalpy and entropy values
4207 HEOS->update(DmolarT_INPUTS, component.triple_vapor.rhomolar, component.triple_vapor.T);
4208 component.triple_vapor.hmolar = HEOS->hmolar();
4209 component.triple_vapor.smolar = HEOS->smolar();
4210
4211 if (!HEOS->is_pure()) {
4212 // Calculate the new enthalpy and entropy values
4213 HEOS->update(DmolarT_INPUTS, component.EOS().max_sat_T.rhomolar, component.EOS().max_sat_T.T);
4214 component.EOS().max_sat_T.hmolar = HEOS->hmolar();
4215 component.EOS().max_sat_T.smolar = HEOS->smolar();
4216 // Calculate the new enthalpy and entropy values
4217 HEOS->update(DmolarT_INPUTS, component.EOS().max_sat_p.rhomolar, component.EOS().max_sat_p.T);
4218 component.EOS().max_sat_p.hmolar = HEOS->hmolar();
4219 component.EOS().max_sat_p.smolar = HEOS->smolar();
4220 }
4221}
4222
4223} /* namespace CoolProp */