CoolProp 7.2.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 } else if (key == "pmin") {
285 return superanc.get_pmin();
286 } else if (key == "Tmin") {
287 return superanc.get_Tmin();
288 } else if (key == "Tcrit_num") {
289 return superanc.get_Tcrit_num();
290 } else {
291 throw ValueError(format("Superancillary parameter [%s] is invalid", key.c_str()));
292 }
293 } else {
294 throw ValueError(format("Superancillary not available for this fluid"));
295 }
296 } else {
297 throw ValueError(format("fluid parameter [%s] is invalid", parameter.c_str()));
298 }
299}
300
301void HelmholtzEOSMixtureBackend::apply_simple_mixing_rule(std::size_t i, std::size_t j, const std::string& model) {
302 // bound-check indices
303 if (i < 0 || i >= N) {
304 if (j < 0 || j >= N) {
305 throw ValueError(format("Both indices i [%d] and j [%d] are out of bounds. Must be between 0 and %d.", i, j, N - 1));
306 } else {
307 throw ValueError(format("Index i [%d] is out of bounds. Must be between 0 and %d.", i, N - 1));
308 }
309 } else if (j < 0 || j >= N) {
310 throw ValueError(format("Index j [%d] is out of bounds. Must be between 0 and %d.", j, N - 1));
311 }
312 if (model == "linear") {
314 double gammaT = 0.5 * (Tc1 + Tc2) / sqrt(Tc1 * Tc2);
316 double gammaV = 4.0 * (1 / rhoc1 + 1 / rhoc2) / pow(pow(rhoc1, -1.0 / 3.0) + pow(rhoc2, -1.0 / 3.0), 3);
317 set_binary_interaction_double(i, j, "betaT", 1.0);
318 set_binary_interaction_double(i, j, "gammaT", gammaT);
319 set_binary_interaction_double(i, j, "betaV", 1.0);
320 set_binary_interaction_double(i, j, "gammaV", gammaV);
321 } else if (model == "Lorentz-Berthelot") {
322 set_binary_interaction_double(i, j, "betaT", 1.0);
323 set_binary_interaction_double(i, j, "gammaT", 1.0);
324 set_binary_interaction_double(i, j, "betaV", 1.0);
325 set_binary_interaction_double(i, j, "gammaV", 1.0);
326 } else {
327 throw ValueError(format("mixing rule [%s] is not understood", model.c_str()));
328 }
329}
331void HelmholtzEOSMixtureBackend::set_binary_interaction_double(const std::size_t i, const std::size_t j, const std::string& parameter,
332 const double value) {
333 // bound-check indices
334 if (i < 0 || i >= N) {
335 if (j < 0 || j >= N) {
336 throw ValueError(format("Both indices i [%d] and j [%d] are out of bounds. Must be between 0 and %d.", i, j, N - 1));
337 } else {
338 throw ValueError(format("Index i [%d] is out of bounds. Must be between 0 and %d.", i, N - 1));
339 }
340 } else if (j < 0 || j >= N) {
341 throw ValueError(format("Index j [%d] is out of bounds. Must be between 0 and %d.", j, N - 1));
342 }
343 if (parameter == "Fij") {
344 residual_helmholtz->Excess.F[i][j] = value;
345 residual_helmholtz->Excess.F[j][i] = value;
346 } else {
347 Reducing->set_binary_interaction_double(i, j, parameter, value);
348 }
350 for (std::vector<shared_ptr<HelmholtzEOSMixtureBackend>>::iterator it = linked_states.begin(); it != linked_states.end(); ++it) {
351 it->get()->set_binary_interaction_double(i, j, parameter, value);
352 }
353};
355double HelmholtzEOSMixtureBackend::get_binary_interaction_double(const std::size_t i, const std::size_t j, const std::string& parameter) {
356 // bound-check indices
357 if (i < 0 || i >= N) {
358 if (j < 0 || j >= N) {
359 throw ValueError(format("Both indices i [%d] and j [%d] are out of bounds. Must be between 0 and %d.", i, j, N - 1));
360 } else {
361 throw ValueError(format("Index i [%d] is out of bounds. Must be between 0 and %d.", i, N - 1));
362 }
363 } else if (j < 0 || j >= N) {
364 throw ValueError(format("Index j [%d] is out of bounds. Must be between 0 and %d.", j, N - 1));
365 }
366 if (parameter == "Fij") {
367 return residual_helmholtz->Excess.F[i][j];
368 } else {
369 return Reducing->get_binary_interaction_double(i, j, parameter);
370 }
371};
373//std::string HelmholtzEOSMixtureBackend::get_binary_interaction_string(const std::string &CAS1, const std::string &CAS2, const std::string &parameter){
374// return CoolProp::get_mixture_binary_pair_data(CAS1, CAS2, parameter);
375//}
377void HelmholtzEOSMixtureBackend::set_binary_interaction_string(const std::size_t i, const std::size_t j, const std::string& parameter,
378 const std::string& value) {
379 // bound-check indices
380 if (i < 0 || i >= N) {
381 if (j < 0 || j >= N) {
382 throw ValueError(format("Both indices i [%d] and j [%d] are out of bounds. Must be between 0 and %d.", i, j, N - 1));
383 } else {
384 throw ValueError(format("Index i [%d] is out of bounds. Must be between 0 and %d.", i, N - 1));
385 }
386 } else if (j < 0 || j >= N) {
387 throw ValueError(format("Index j [%d] is out of bounds. Must be between 0 and %d.", j, N - 1));
388 }
389 if (parameter == "function") {
390 residual_helmholtz->Excess.DepartureFunctionMatrix[i][j].reset(get_departure_function(value));
391 residual_helmholtz->Excess.DepartureFunctionMatrix[j][i].reset(get_departure_function(value));
392 } else {
393 throw ValueError(format("Cannot process this string parameter [%s] in set_binary_interaction_string", parameter.c_str()));
394 }
396 for (std::vector<shared_ptr<HelmholtzEOSMixtureBackend>>::iterator it = linked_states.begin(); it != linked_states.end(); ++it) {
397 it->get()->set_binary_interaction_string(i, j, parameter, value);
398 }
399};
400
401void HelmholtzEOSMixtureBackend::calc_change_EOS(const std::size_t i, const std::string& EOS_name) {
402
403 if (i < components.size()) {
404 CoolPropFluid& fluid = components[i];
405 EquationOfState& EOS = fluid.EOSVector[0];
406
407 if (EOS_name == "SRK" || EOS_name == "Peng-Robinson") {
408
409 // Get the parameters for the cubic EOS
410 CoolPropDbl Tc = EOS.reduce.T;
411 CoolPropDbl pc = EOS.reduce.p;
412 CoolPropDbl rhomolarc = EOS.reduce.rhomolar;
413 CoolPropDbl acentric = EOS.acentric;
414 CoolPropDbl R = 8.3144598;
415
416 // Remove the residual part
417 EOS.alphar.empty_the_EOS();
418 // Set the contribution
419 shared_ptr<AbstractCubic> ac;
420 if (EOS_name == "SRK") {
421 ac.reset(new SRK(Tc, pc, acentric, R));
422 } else {
423 ac.reset(new PengRobinson(Tc, pc, acentric, R));
424 }
425 ac->set_Tr(Tc);
426 ac->set_rhor(rhomolarc);
428 } else if (EOS_name == "XiangDeiters") {
429
430 // Get the parameters for the EOS
431 CoolPropDbl Tc = EOS.reduce.T;
432 CoolPropDbl pc = EOS.reduce.p;
433 CoolPropDbl rhomolarc = EOS.reduce.rhomolar;
434 CoolPropDbl acentric = EOS.acentric;
435 CoolPropDbl R = 8.3144598;
436
437 // Remove the residual part
438 EOS.alphar.empty_the_EOS();
439 // Set the Xiang & Deiters contribution
440 EOS.alphar.XiangDeiters = ResidualHelmholtzXiangDeiters(Tc, pc, rhomolarc, acentric, R);
441 }
442 } else {
443 throw ValueError(format("Index [%d] is invalid", i));
444 }
445 // Now do the same thing to the saturated liquid and vapor instances if possible
446 if (this->SatL) SatL->change_EOS(i, EOS_name);
447 if (this->SatV) SatV->change_EOS(i, EOS_name);
448}
450 // Clear the phase envelope data
452 // Build the phase envelope
453 PhaseEnvelopeRoutines::build(*this, type);
454 // Finalize the phase envelope
456};
458 // Build the matrix of binary-pair reducing functions
460}
462 CoolPropFluid& component = components[0];
463 EquationOfState& EOS = component.EOSVector[0];
464
465 // Clear the state class
466 clear();
467
468 // Calculate the new enthalpy and entropy values
470 EOS.hs_anchor.hmolar = hmolar();
471 EOS.hs_anchor.smolar = smolar();
472
473 // Calculate the new enthalpy and entropy values at the reducing state
475 EOS.reduce.hmolar = hmolar();
476 EOS.reduce.smolar = smolar();
477
478 // Clear again just to be sure
479 clear();
480}
483 if (!state.compare("hs_anchor")) {
484 return components[0].EOS().hs_anchor;
485 } else if (!state.compare("max_sat_T")) {
486 return components[0].EOS().max_sat_T;
487 } else if (!state.compare("max_sat_p")) {
488 return components[0].EOS().max_sat_p;
489 } else if (!state.compare("reducing")) {
490 return components[0].EOS().reduce;
491 } else if (!state.compare("critical")) {
492 return components[0].crit;
493 } else if (!state.compare("triple_liquid")) {
494 return components[0].triple_liquid;
495 } else if (!state.compare("triple_vapor")) {
496 return components[0].triple_vapor;
497 } else {
498 throw ValueError(format("This state [%s] is invalid to calc_state", state.c_str()));
499 }
500 } else {
501 if (!state.compare("critical")) {
502 return _critical;
503 } else {
504 throw ValueError(format("calc_state not supported for mixtures"));
505 }
506 }
507};
510 return components[0].EOS().acentric;
511 } else {
512 throw ValueError("acentric factor cannot be calculated for mixtures");
513 }
514}
517 return components[0].gas_constant();
518 } else {
519 if (get_config_bool(NORMALIZE_GAS_CONSTANTS)) {
520 return get_config_double(R_U_CODATA);
521 } else {
522 // mass fraction weighted average of the components
523 double summer = 0;
524 for (unsigned int i = 0; i < components.size(); ++i) {
525 summer += mole_fractions[i] * components[i].gas_constant();
526 }
527 return summer;
528 }
529 }
530}
532 double summer = 0;
533 for (unsigned int i = 0; i < components.size(); ++i) {
534 summer += mole_fractions[i] * components[i].molar_mass();
535 }
536 return summer;
537}
540 if (param == iP && given == iT) {
541 // p = f(T), direct evaluation
542 switch (Q) {
543 case 0:
544 return components[0].ancillaries.pL.evaluate(value);
545 case 1:
546 return components[0].ancillaries.pV.evaluate(value);
547 }
548 } else if (param == iT && given == iP) {
549 // T = f(p), inverse evaluation
550 switch (Q) {
551 case 0:
552 return components[0].ancillaries.pL.invert(value);
553 case 1:
554 return components[0].ancillaries.pV.invert(value);
555 }
556 } else if (param == iDmolar && given == iT) {
557 // rho = f(T), inverse evaluation
558 switch (Q) {
559 case 0:
560 return components[0].ancillaries.rhoL.evaluate(value);
561 case 1:
562 return components[0].ancillaries.rhoV.evaluate(value);
563 }
564 } else if (param == iT && given == iDmolar) {
565 // T = f(rho), inverse evaluation
566 switch (Q) {
567 case 0:
568 return components[0].ancillaries.rhoL.invert(value);
569 case 1:
570 return components[0].ancillaries.rhoV.invert(value);
571 }
572 } else if (param == isurface_tension && given == iT) {
573 return components[0].ancillaries.surface_tension.evaluate(value);
574 } else {
575 throw ValueError(format("calc of %s given %s is invalid in calc_saturation_ancillary", get_parameter_information(param, "short").c_str(),
576 get_parameter_information(given, "short").c_str()));
577 }
578
579 throw ValueError(format("Q [%d] is invalid in calc_saturation_ancillary", Q));
580 } else {
581 throw NotImplementedError(format("calc_saturation_ancillary not implemented for mixtures"));
582 }
583}
584
587 return components[0].ancillaries.melting_line.evaluate(param, given, value);
588 } else {
589 throw NotImplementedError(format("calc_melting_line not implemented for mixtures"));
590 }
591}
594 if ((_phase == iphase_twophase) || (_phase == iphase_critical_point)) { // if within the two phase region or at critical point
595 return components[0].ancillaries.surface_tension.evaluate(T()); // calculate surface tension and return
596 } else { // else state point not in the two phase region
597 throw ValueError(format("surface tension is only defined within the two-phase region; Try PQ or QT inputs")); // throw error
598 }
599 } else {
600 throw NotImplementedError(format("surface tension not implemented for mixtures"));
601 }
602}
605 CoolPropDbl eta_dilute;
606 switch (components[0].transport.viscosity_dilute.type) {
609 break;
612 break;
615 break;
618 break;
621 break;
624 break;
627 break;
630 break;
631 default:
632 throw ValueError(
633 format("dilute viscosity type [%d] is invalid for fluid %s", components[0].transport.viscosity_dilute.type, name().c_str()));
634 }
635 return eta_dilute;
636 } else {
637 throw NotImplementedError(format("dilute viscosity not implemented for mixtures"));
638 }
639}
641 CoolPropDbl eta_dilute = calc_viscosity_dilute(), initial_density = 0, residual = 0;
642 return calc_viscosity_background(eta_dilute, initial_density, residual);
643}
645
646 switch (components[0].transport.viscosity_initial.type) {
649 CoolPropDbl rho = rhomolar();
650 initial_density = eta_dilute * B_eta_initial * rho; //TODO: Check units once AMTG
651 break;
652 }
655 break;
656 }
658 break;
659 }
660 }
661
662 // Higher order terms
663 switch (components[0].transport.viscosity_higher_order.type) {
666 break;
668 residual = TransportRoutines::viscosity_higher_order_friction_theory(*this);
669 break;
672 break;
675 break;
678 break;
681 break;
684 break;
687 break;
690 break;
691 default:
692 throw ValueError(
693 format("higher order viscosity type [%d] is invalid for fluid %s", components[0].transport.viscosity_dilute.type, name().c_str()));
694 }
695
696 return initial_density + residual;
697}
698
701 CoolPropDbl dilute = 0, initial_density = 0, residual = 0, critical = 0;
702 calc_viscosity_contributions(dilute, initial_density, residual, critical);
703 return dilute + initial_density + residual + critical;
704 } else {
705 set_warning_string("Mixture model for viscosity is highly approximate");
706 CoolPropDbl summer = 0;
707 for (std::size_t i = 0; i < mole_fractions.size(); ++i) {
708 shared_ptr<HelmholtzEOSBackend> HEOS(new HelmholtzEOSBackend(components[i]));
709 HEOS->update(DmolarT_INPUTS, _rhomolar, _T);
710 summer += mole_fractions[i] * log(HEOS->viscosity());
711 }
712 return exp(summer);
713 }
714}
716 CoolPropDbl& critical) {
718 // Reset the variables
719 dilute = 0;
720 initial_density = 0;
721 residual = 0;
722 critical = 0;
723
724 // Get a reference for code cleanness
725 CoolPropFluid& component = components[0];
726
727 if (!component.transport.viscosity_model_provided) {
728 throw ValueError(format("Viscosity model is not available for this fluid"));
729 }
730
731 // Check if using ECS
732 if (component.transport.viscosity_using_ECS) {
733 // Get reference fluid name
734 std::string fluid_name = component.transport.viscosity_ecs.reference_fluid;
735 std::vector<std::string> names(1, fluid_name);
736 // Get a managed pointer to the reference fluid for ECS
737 shared_ptr<HelmholtzEOSMixtureBackend> ref_fluid(new HelmholtzEOSMixtureBackend(names));
738 // Get the viscosity using ECS and stick in the critical value
739 critical = TransportRoutines::viscosity_ECS(*this, *ref_fluid);
740 return;
741 }
742
743 // Check if using Chung model
744 if (component.transport.viscosity_using_Chung) {
745 // Get the viscosity using ECS and stick in the critical value
746 critical = TransportRoutines::viscosity_Chung(*this);
747 return;
748 }
749
750 // Check if using rho*sr model
751 if (component.transport.viscosity_using_rhosr) {
752 // Get the viscosity using rho*sr model and stick in the critical value
753 critical = TransportRoutines::viscosity_rhosr(*this);
754 return;
755 }
756
758 // Evaluate hardcoded model and stick in the critical value
759 switch (component.transport.hardcoded_viscosity) {
762 break;
765 break;
768 break;
771 break;
774 break;
777 break;
780 break;
783 break;
784 default:
785 throw ValueError(
786 format("hardcoded viscosity type [%d] is invalid for fluid %s", component.transport.hardcoded_viscosity, name().c_str()));
787 }
788 return;
789 }
790
791 // -------------------------
792 // Normal evaluation
793 // -------------------------
794
795 // Dilute part
796 dilute = calc_viscosity_dilute();
797
798 // Background viscosity given by the sum of the initial density dependence and higher order terms
799 calc_viscosity_background(dilute, initial_density, residual);
800
801 // Critical part (no fluids have critical enhancement for viscosity currently)
802 critical = 0;
803 } else {
804 throw ValueError("calc_viscosity_contributions invalid for mixtures");
805 }
806}
808 CoolPropDbl& critical) {
810 // Reset the variables
811 dilute = 0;
812 initial_density = 0;
813 residual = 0;
814 critical = 0;
815
816 // Get a reference for code cleanness
817 CoolPropFluid& component = components[0];
818
819 if (!component.transport.conductivity_model_provided) {
820 throw ValueError(format("Thermal conductivity model is not available for this fluid"));
821 }
822
823 // Check if using ECS
824 if (component.transport.conductivity_using_ECS) {
825 // Get reference fluid name
826 std::string fluid_name = component.transport.conductivity_ecs.reference_fluid;
827 std::vector<std::string> name(1, fluid_name);
828 // Get a managed pointer to the reference fluid for ECS
829 shared_ptr<HelmholtzEOSMixtureBackend> ref_fluid(new HelmholtzEOSMixtureBackend(name));
830 // Get the viscosity using ECS and store in initial_density (not normally used);
831 initial_density = TransportRoutines::conductivity_ECS(*this, *ref_fluid); // Warning: not actually initial_density
832 return;
833 }
834
836 // Evaluate hardcoded model and deposit in initial_density variable
837 // Warning: not actually initial_density
838 switch (component.transport.hardcoded_conductivity) {
840 initial_density = TransportRoutines::conductivity_hardcoded_water(*this);
841 break;
843 initial_density = TransportRoutines::conductivity_hardcoded_heavywater(*this);
844 break;
846 initial_density = TransportRoutines::conductivity_hardcoded_R23(*this);
847 break;
849 initial_density = TransportRoutines::conductivity_hardcoded_helium(*this);
850 break;
852 initial_density = TransportRoutines::conductivity_hardcoded_methane(*this);
853 break;
854 default:
855 throw ValueError(format("hardcoded conductivity type [%d] is invalid for fluid %s",
856 components[0].transport.hardcoded_conductivity, name().c_str()));
857 }
858 return;
859 }
860
861 // -------------------------
862 // Normal evaluation
863 // -------------------------
864
865 // Dilute part
866 switch (component.transport.conductivity_dilute.type) {
868 dilute = TransportRoutines::conductivity_dilute_ratio_polynomials(*this);
869 break;
871 dilute = TransportRoutines::conductivity_dilute_eta0_and_poly(*this);
872 break;
874 dilute = TransportRoutines::conductivity_dilute_hardcoded_CO2(*this);
875 break;
877 dilute = TransportRoutines::conductivity_dilute_hardcoded_CO2_HuberJPCRD2016(*this);
878 break;
880 dilute = TransportRoutines::conductivity_dilute_hardcoded_ethane(*this);
881 break;
883 dilute = 0.0;
884 break;
885 default:
886 throw ValueError(
887 format("dilute conductivity type [%d] is invalid for fluid %s", components[0].transport.conductivity_dilute.type, name().c_str()));
888 }
889
890 // Residual part
891 residual = calc_conductivity_background();
892
893 // Critical part
894 switch (component.transport.conductivity_critical.type) {
896 critical = TransportRoutines::conductivity_critical_simplified_Olchowy_Sengers(*this);
897 break;
899 critical = TransportRoutines::conductivity_critical_hardcoded_R123(*this);
900 break;
902 critical = TransportRoutines::conductivity_critical_hardcoded_ammonia(*this);
903 break;
905 critical = 0.0;
906 break;
908 critical = TransportRoutines::conductivity_critical_hardcoded_CO2_ScalabrinJPCRD2006(*this);
909 break;
910 default:
911 throw ValueError(
912 format("critical conductivity type [%d] is invalid for fluid %s", components[0].transport.viscosity_dilute.type, name().c_str()));
913 }
914 } else {
915 throw ValueError("calc_conductivity_contributions invalid for mixtures");
916 }
917};
918
920 // Residual part
921 CoolPropDbl lambda_residual = _HUGE;
922 switch (components[0].transport.conductivity_residual.type) {
924 lambda_residual = TransportRoutines::conductivity_residual_polynomial(*this);
925 break;
927 lambda_residual = TransportRoutines::conductivity_residual_polynomial_and_exponential(*this);
928 break;
929 default:
930 throw ValueError(
931 format("residual conductivity type [%d] is invalid for fluid %s", components[0].transport.conductivity_residual.type, name().c_str()));
932 }
933 return lambda_residual;
934}
937 CoolPropDbl dilute = 0, initial_density = 0, residual = 0, critical = 0;
938 calc_conductivity_contributions(dilute, initial_density, residual, critical);
939 return dilute + initial_density + residual + critical;
940 } else {
941 set_warning_string("Mixture model for conductivity is highly approximate");
942 CoolPropDbl summer = 0;
943 for (std::size_t i = 0; i < mole_fractions.size(); ++i) {
944 shared_ptr<HelmholtzEOSBackend> HEOS(new HelmholtzEOSBackend(components[i]));
945 HEOS->update(DmolarT_INPUTS, _rhomolar, _T);
946 summer += mole_fractions[i] * HEOS->conductivity();
947 }
948 return summer;
949 }
950}
951void HelmholtzEOSMixtureBackend::calc_conformal_state(const std::string& reference_fluid, CoolPropDbl& T, CoolPropDbl& rhomolar) {
952 shared_ptr<CoolProp::HelmholtzEOSMixtureBackend> REF(new CoolProp::HelmholtzEOSBackend(reference_fluid));
953
954 if (T < 0 && rhomolar < 0) {
955 // Collect some parameters
956 CoolPropDbl Tc = T_critical(), Tc0 = REF->T_critical(), rhocmolar = rhomolar_critical(), rhocmolar0 = REF->rhomolar_critical();
957
958 // Starting guess values for shape factors
959 CoolPropDbl theta = 1;
960 CoolPropDbl phi = 1;
961
962 // The equivalent substance reducing ratios
963 CoolPropDbl f = Tc / Tc0 * theta;
964 CoolPropDbl h = rhocmolar0 / rhocmolar * phi; // Must be the ratio of MOLAR densities!!
965
966 // Starting guesses for conformal state
967 T = this->T() / f;
968 rhomolar = this->rhomolar() * h;
969 }
970
971 TransportRoutines::conformal_state_solver(*this, *REF, T, rhomolar);
972}
974 double summer = 0;
975 for (unsigned int i = 0; i < components.size(); ++i) {
976 summer += mole_fractions[i] * components[i].EOS().Ttriple;
977 }
978 return summer;
979}
981 double summer = 0;
982 for (unsigned int i = 0; i < components.size(); ++i) {
983 summer += mole_fractions[i] * components[i].EOS().ptriple;
984 }
985 return summer;
986}
988 if (components.size() != 1) {
989 throw ValueError(format("calc_name is only valid for pure and pseudo-pure fluids, %d components", components.size()));
990 } else {
991 return components[0].name;
992 }
993}
994
996 if ((key == iDmolar) && _rhoLmolar) return _rhoLmolar;
997 if (!SatL) throw ValueError("The saturated liquid state has not been set.");
998 return SatL->keyed_output(key);
999}
1001 if ((key == iDmolar) && _rhoVmolar) return _rhoVmolar;
1002 if (!SatV) throw ValueError("The saturated vapor state has not been set.");
1003 return SatV->keyed_output(key);
1004}
1005
1006void HelmholtzEOSMixtureBackend::calc_ideal_curve(const std::string& type, std::vector<double>& T, std::vector<double>& p) {
1007 if (type == "Joule-Thomson") {
1008 JouleThomsonCurveTracer JTCT(this, 1e5, 800);
1009 JTCT.trace(T, p);
1010 } else if (type == "Joule-Inversion") {
1011 JouleInversionCurveTracer JICT(this, 1e5, 800);
1012 JICT.trace(T, p);
1013 } else if (type == "Ideal") {
1014 IdealCurveTracer ICT(this, 1e5, 800);
1015 ICT.trace(T, p);
1016 } else if (type == "Boyle") {
1017 BoyleCurveTracer BCT(this, 1e5, 800);
1018 BCT.trace(T, p);
1019 } else {
1020 throw ValueError(format("Invalid ideal curve type: %s", type.c_str()));
1021 }
1022};
1023std::vector<std::string> HelmholtzEOSMixtureBackend::calc_fluid_names(void) {
1024 std::vector<std::string> out;
1025 for (std::size_t i = 0; i < components.size(); ++i) {
1026 out.push_back(components[i].name);
1027 }
1028 return out;
1029}
1031 if (components.size() != 1) {
1032 throw ValueError(format("For now, calc_ODP is only valid for pure and pseudo-pure fluids, %d components", components.size()));
1033 } else {
1034 CoolPropDbl v = components[0].environment.ODP;
1035 if (!ValidNumber(v) || v < 0) {
1036 throw ValueError(format("ODP value is not specified or invalid"));
1037 }
1038 return v;
1039 }
1040}
1042 if (components.size() != 1) {
1043 throw ValueError(format("For now, calc_GWP20 is only valid for pure and pseudo-pure fluids, %d components", components.size()));
1044 } else {
1045 CoolPropDbl v = components[0].environment.GWP20;
1046 if (!ValidNumber(v) || v < 0) {
1047 throw ValueError(format("GWP20 value is not specified or invalid"));
1048 }
1049 return v;
1050 }
1051}
1053 if (components.size() != 1) {
1054 throw ValueError(format("For now, calc_GWP100 is only valid for pure and pseudo-pure fluids, %d components", components.size()));
1055 } else {
1056 CoolPropDbl v = components[0].environment.GWP100;
1057 if (!ValidNumber(v) || v < 0) {
1058 throw ValueError(format("GWP100 value is not specified or invalid"));
1059 }
1060 return v;
1061 }
1062}
1064 if (components.size() != 1) {
1065 throw ValueError(format("For now, calc_GWP500 is only valid for pure and pseudo-pure fluids, %d components", components.size()));
1066 } else {
1067 CoolPropDbl v = components[0].environment.GWP500;
1068 if (!ValidNumber(v) || v < 0) {
1069 throw ValueError(format("GWP500 value is not specified or invalid"));
1070 }
1071 return v;
1072 }
1073}
1075 if (components.size() != 1) {
1076 std::vector<CriticalState> critpts = calc_all_critical_points();
1077 if (critpts.size() == 1) {
1078 //if (!critpts[0].stable){ throw ValueError(format("found one critical point but critical point is not stable")); }
1079 return critpts[0].T;
1080 } else {
1081 throw ValueError(format("critical point finding routine found %d critical points", critpts.size()));
1082 }
1083 } else {
1084 if (get_config_bool(ENABLE_SUPERANCILLARIES) && is_pure()) {
1085 auto& optsuperanc = get_superanc_optional();
1086 if (optsuperanc) {
1087 return optsuperanc.value().get_Tcrit_num(); // from the numerical critical point satisfying dp/drho|T = d2p/drho2|T = 0
1088 }
1089 }
1090 return components[0].crit.T;
1091 }
1092}
1094 if (components.size() != 1) {
1095 std::vector<CriticalState> critpts = calc_all_critical_points();
1096 if (critpts.size() == 1) {
1097 //if (!critpts[0].stable){ throw ValueError(format("found one critical point but critical point is not stable")); }
1098 return critpts[0].p;
1099 } else {
1100 throw ValueError(format("critical point finding routine found %d critical points", critpts.size()));
1101 }
1102 } else {
1103 if (get_config_bool(ENABLE_SUPERANCILLARIES) && is_pure()) {
1104 auto& optsuperanc = get_superanc_optional();
1105 if (optsuperanc) {
1106 return optsuperanc.value().get_pmax(); // from the numerical critical point satisfying dp/drho|T = d2p/drho2|T = 0
1107 }
1108 }
1109 return components[0].crit.p;
1110 }
1111}
1113 if (components.size() != 1) {
1114 std::vector<CriticalState> critpts = calc_all_critical_points();
1115 if (critpts.size() == 1) {
1116 //if (!critpts[0].stable){ throw ValueError(format("found one critical point but critical point is not stable")); }
1117 return critpts[0].rhomolar;
1118 } else {
1119 throw ValueError(format("critical point finding routine found %d critical points", critpts.size()));
1120 }
1121 } else {
1122 if (get_config_bool(ENABLE_SUPERANCILLARIES) && is_pure()) {
1123 auto& optsuperanc = get_superanc_optional();
1124 if (optsuperanc) {
1125 return optsuperanc.value().get_rhocrit_num(); // from the numerical critical point satisfying dp/drho|T = d2p/drho2|T = 0
1126 }
1127 }
1128 return components[0].crit.rhomolar;
1129 }
1130}
1133 if (components[0].EOS().pseudo_pure) {
1134 return components[0].EOS().max_sat_p.p;
1135 } else {
1136 return p_critical();
1137 }
1138 } else {
1139 throw ValueError("calc_pmax_sat not yet defined for mixtures");
1140 }
1141}
1144 if (components[0].EOS().pseudo_pure) {
1145 double Tmax_sat = components[0].EOS().max_sat_T.T;
1146 if (!ValidNumber(Tmax_sat)) {
1147 return T_critical();
1148 } else {
1149 return Tmax_sat;
1150 }
1151 } else {
1152 return T_critical();
1153 }
1154 } else {
1155 throw ValueError("calc_Tmax_sat not yet defined for mixtures");
1156 }
1157}
1158
1161 Tmin_satL = components[0].EOS().sat_min_liquid.T;
1162 Tmin_satV = components[0].EOS().sat_min_vapor.T;
1163 return;
1164 } else {
1165 throw ValueError("calc_Tmin_sat not yet defined for mixtures");
1166 }
1167}
1168
1171 pmin_satL = components[0].EOS().sat_min_liquid.p;
1172 pmin_satV = components[0].EOS().sat_min_vapor.p;
1173 return;
1174 } else {
1175 throw ValueError("calc_pmin_sat not yet defined for mixtures");
1176 }
1177}
1178
1179// Minimum allowed saturation temperature the maximum of the saturation temperatures of liquid and vapor
1180// For pure fluids, both values are the same, for pseudo-pure they are probably the same, for mixtures they are definitely not the same
1181
1183 double summer = 0;
1184 for (unsigned int i = 0; i < components.size(); ++i) {
1185 summer += mole_fractions[i] * components[i].EOS().limits.Tmax;
1186 }
1187 return summer;
1188}
1190 double summer = 0;
1191 for (unsigned int i = 0; i < components.size(); ++i) {
1192 summer += mole_fractions[i] * components[i].EOS().limits.Tmin;
1193 }
1194 return summer;
1195}
1197 double summer = 0;
1198 for (unsigned int i = 0; i < components.size(); ++i) {
1199 summer += mole_fractions[i] * components[i].EOS().limits.pmax;
1200 }
1201 return summer;
1202}
1203
1205 clear();
1206 _Q = Q;
1207 _T = T;
1208 if (!is_pure()) {
1209 throw ValueError(format("Is not a pure fluid"));
1210 }
1211 if (!get_config_bool(ENABLE_SUPERANCILLARIES)) {
1212 throw ValueError(format("Superancillaries are not enabled"));
1213 }
1214 auto& optsuperanc = get_superanc_optional();
1215 if (!optsuperanc) {
1216 throw ValueError(format("Superancillaries not available for this fluid"));
1217 }
1218
1219 auto& superanc = optsuperanc.value();
1220 CoolPropDbl Tcrit_num = superanc.get_Tcrit_num();
1221 if (T > Tcrit_num) {
1222 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));
1223 }
1224 auto rhoL = superanc.eval_sat(T, 'D', 0);
1225 auto rhoV = superanc.eval_sat(T, 'D', 1);
1226 auto p = superanc.eval_sat(T, 'P', 1);
1227 SatL->update_TDmolarP_unchecked(T, rhoL, p);
1228 SatV->update_TDmolarP_unchecked(T, rhoV, p);
1229 _p = p;
1230 _rhomolar = 1 / (Q / rhoV + (1 - Q) / rhoL);
1232
1233 post_update(false /*optional_checks*/);
1234}
1235
1237
1238 clear();
1239
1241 _T = T;
1242 _p = p;
1243
1244 // Cleanup
1245 bool optional_checks = false;
1246 post_update(optional_checks);
1247}
1248
1250 // TODO: This is just a quick fix for #878 - should be done more systematically
1251 const CoolPropDbl rhomolar_min = 0;
1252 const CoolPropDbl T_min = 0;
1253
1254 if (rhomolar < rhomolar_min) {
1255 throw ValueError(format("The molar density of %f mol/m3 is below the minimum of %f mol/m3", rhomolar, rhomolar_min));
1256 }
1257
1258 if (T < T_min) {
1259 throw ValueError(format("The temperature of %f K is below the minimum of %f K", T, T_min));
1260 }
1261
1263 // Set up the state
1264 pre_update(pair, rhomolar, T);
1265
1267 _T = T;
1268 _p = calc_pressure();
1269
1270 // Cleanup
1271 bool optional_checks = false;
1272 post_update(optional_checks);
1273}
1274
1277 // Set up the state
1278 pre_update(pair, hmolar, Q);
1279
1280 _hmolar = hmolar;
1281 _Q = Q;
1282 FlashRoutines::HQ_flash(*this, Tguess);
1283
1284 // Cleanup
1285 post_update();
1286}
1288 this->_hmolar = HEOS.hmolar();
1289 this->_smolar = HEOS.smolar();
1290 this->_T = HEOS.T();
1291 this->_umolar = HEOS.umolar();
1292 this->_p = HEOS.p();
1293 this->_rhomolar = HEOS.rhomolar();
1294 this->_Q = HEOS.Q();
1295 this->_phase = HEOS.phase();
1296
1297 // Copy the derivatives as well
1298}
1301 // Set up the state
1302 pre_update(pair, p, T);
1303
1304 // Do the flash call to find rho = f(T,p)
1305 CoolPropDbl rhomolar = solver_rho_Tp(T, p, rhomolar_guess);
1306
1307 // Update the class with the new calculated density
1309
1310 // Skip the cleanup, already done in update_DmolarT_direct
1311}
1312
1314 // Clear the state
1315 clear();
1316
1317 if (is_pure_or_pseudopure == false && mole_fractions.size() == 0) {
1318 throw ValueError("Mole fractions must be set");
1319 }
1320
1321 // If the inputs are in mass units, convert them to molar units
1322 mass_to_molar_inputs(input_pair, value1, value2);
1323
1324 // Set the mole-fraction weighted gas constant for the mixture
1325 // (or the pure/pseudo-pure fluid) if it hasn't been set yet
1326 gas_constant();
1327
1328 // Calculate and cache the reducing state
1330}
1331
1332void HelmholtzEOSMixtureBackend::update(CoolProp::input_pairs input_pair, double value1, double value2) {
1333 if (get_debug_level() > 10) {
1334 std::cout << format("%s (%d): update called with (%d: (%s), %g, %g)", __FILE__, __LINE__, input_pair,
1335 get_input_pair_short_desc(input_pair).c_str(), value1, value2)
1336 << std::endl;
1337 }
1338
1339 CoolPropDbl ld_value1 = value1, ld_value2 = value2;
1340 pre_update(input_pair, ld_value1, ld_value2);
1341 value1 = ld_value1;
1342 value2 = ld_value2;
1343
1344 switch (input_pair) {
1345 case PT_INPUTS:
1346 _p = value1;
1347 _T = value2;
1349 break;
1350 case DmolarT_INPUTS:
1351 _rhomolar = value1;
1352 _T = value2;
1354 break;
1355 case SmolarT_INPUTS:
1356 _smolar = value1;
1357 _T = value2;
1359 break;
1360 //case HmolarT_INPUTS:
1361 // _hmolar = value1; _T = value2; FlashRoutines::DHSU_T_flash(*this, iHmolar); break;
1362 //case TUmolar_INPUTS:
1363 // _T = value1; _umolar = value2; FlashRoutines::DHSU_T_flash(*this, iUmolar); break;
1364 case DmolarP_INPUTS:
1365 _rhomolar = value1;
1366 _p = value2;
1368 break;
1370 _rhomolar = value1;
1371 _hmolar = value2;
1373 break;
1375 _rhomolar = value1;
1376 _smolar = value2;
1378 break;
1380 _rhomolar = value1;
1381 _umolar = value2;
1383 break;
1384 case HmolarP_INPUTS:
1385 _hmolar = value1;
1386 _p = value2;
1388 break;
1389 case PSmolar_INPUTS:
1390 _p = value1;
1391 _smolar = value2;
1393 break;
1394 case PUmolar_INPUTS:
1395 _p = value1;
1396 _umolar = value2;
1398 break;
1400 _hmolar = value1;
1401 _smolar = value2;
1403 break;
1404 case QT_INPUTS:
1405 _Q = value1;
1406 _T = value2;
1407 if ((_Q < 0) || (_Q > 1)) throw CoolProp::OutOfRangeError("Input vapor quality [Q] must be between 0 and 1");
1409 break;
1410 case PQ_INPUTS:
1411 _p = value1;
1412 _Q = value2;
1413 if ((_Q < 0) || (_Q > 1)) throw CoolProp::OutOfRangeError("Input vapor quality [Q] must be between 0 and 1");
1415 break;
1416 case QSmolar_INPUTS:
1417 _Q = value1;
1418 _smolar = value2;
1419 if ((_Q < 0) || (_Q > 1)) throw CoolProp::OutOfRangeError("Input vapor quality [Q] must be between 0 and 1");
1421 break;
1422 case HmolarQ_INPUTS:
1423 _hmolar = value1;
1424 _Q = value2;
1425 if ((_Q < 0) || (_Q > 1)) throw CoolProp::OutOfRangeError("Input vapor quality [Q] must be between 0 and 1");
1427 break;
1428 case DmolarQ_INPUTS:
1429 _rhomolar = value1;
1430 _Q = value2;
1431 if ((_Q < 0) || (_Q > 1)) throw CoolProp::OutOfRangeError("Input vapor quality [Q] must be between 0 and 1");
1433 break;
1434 default:
1435 throw ValueError(format("This pair of inputs [%s] is not yet supported", get_input_pair_short_desc(input_pair).c_str()));
1436 }
1437
1438 post_update();
1439}
1440const std::vector<CoolPropDbl> HelmholtzEOSMixtureBackend::calc_mass_fractions() {
1441 // mass fraction is mass_i/total_mass;
1442 CoolPropDbl mm = molar_mass();
1443 std::vector<CoolPropDbl>& mole_fractions = get_mole_fractions_ref();
1444 std::vector<CoolPropDbl> mass_fractions(mole_fractions.size());
1445 for (std::size_t i = 0; i < mole_fractions.size(); ++i) {
1446 double mmi = get_fluid_constant(i, imolar_mass);
1447 mass_fractions[i] = mmi * (mole_fractions[i]) / mm;
1448 }
1449 return mass_fractions;
1450}
1451
1453 const GuessesStructure& guesses) {
1454 if (get_debug_level() > 10) {
1455 std::cout << format("%s (%d): update called with (%d: (%s), %g, %g)", __FILE__, __LINE__, input_pair,
1456 get_input_pair_short_desc(input_pair).c_str(), value1, value2)
1457 << std::endl;
1458 }
1459
1460 CoolPropDbl ld_value1 = value1, ld_value2 = value2;
1461 pre_update(input_pair, ld_value1, ld_value2);
1462 value1 = ld_value1;
1463 value2 = ld_value2;
1464
1465 switch (input_pair) {
1466 case PQ_INPUTS:
1467 _p = value1;
1468 _Q = value2;
1470 break;
1471 case QT_INPUTS:
1472 _Q = value1;
1473 _T = value2;
1475 break;
1476 case PT_INPUTS:
1477 _p = value1;
1478 _T = value2;
1480 break;
1481 default:
1482 throw ValueError(format("This pair of inputs [%s] is not yet supported", get_input_pair_short_desc(input_pair).c_str()));
1483 }
1484 post_update();
1485}
1486
1488 // Check the values that must always be set
1489 //if (_p < 0){ throw ValueError("p is less than zero");}
1490 if (!ValidNumber(_p)) {
1491 throw ValueError("p is not a valid number");
1492 }
1493 //if (_T < 0){ throw ValueError("T is less than zero");}
1494 if (!ValidNumber(_T)) {
1495 throw ValueError("T is not a valid number");
1496 }
1497 if (_rhomolar < 0) {
1498 throw ValueError("rhomolar is less than zero");
1499 }
1500 if (!ValidNumber(_rhomolar)) {
1501 throw ValueError("rhomolar is not a valid number");
1502 }
1503
1504 if (optional_checks) {
1505 if (!ValidNumber(_Q)) {
1506 throw ValueError("Q is not a valid number");
1507 }
1508 if (_phase == iphase_unknown) {
1509 throw ValueError("_phase is unknown");
1510 }
1511 }
1512
1513 // Set the reduced variables
1514 _tau = _reducing.T / _T;
1516
1517 // Update the terms in the excess contribution
1518 if (!is_pure_or_pseudopure) {
1519 residual_helmholtz->Excess.update(_tau, _delta);
1520 }
1521}
1522
1524 return 1 / rhomolar_reducing() * calc_alphar_deriv_nocache(0, 1, mole_fractions, _tau, 1e-12);
1525}
1528 CoolPropDbl dtau_dT = -red.T / pow(_T, 2);
1529 return 1 / red.rhomolar * calc_alphar_deriv_nocache(1, 1, mole_fractions, _tau, 1e-12) * dtau_dT;
1530}
1532 return 1 / pow(rhomolar_reducing(), 2) * calc_alphar_deriv_nocache(0, 2, mole_fractions, _tau, 1e-12);
1533}
1536 CoolPropDbl dtau_dT = -red.T / pow(_T, 2);
1537 return 1 / pow(red.rhomolar, 2) * calc_alphar_deriv_nocache(1, 2, mole_fractions, _tau, 1e-12) * dtau_dT;
1538}
1540 /*
1541 Determine the phase given p and one other state variable
1542 */
1543 saturation_called = false;
1544
1545 // Reference declaration to save indexing
1546 CoolPropFluid& component = components[0];
1547
1548 // Maximum saturation temperature - Equal to critical pressure for pure fluids
1549 CoolPropDbl psat_max = calc_pmax_sat();
1550
1551 double T_crit_ = T_critical(), p_crit_ = p_critical(), rhomolar_crit_ = rhomolar_critical();
1552 auto smolar_critical = [this, &T_crit_, &rhomolar_crit_]() { return this->calc_smolar_nocache(T_crit_, rhomolar_crit_); };
1553 auto hmolar_critical = [this, &T_crit_, &rhomolar_crit_]() { return this->calc_hmolar_nocache(T_crit_, rhomolar_crit_); };
1554 auto umolar_critical = [this, &T_crit_, &rhomolar_crit_]() { return this->calc_umolar_nocache(T_crit_, rhomolar_crit_); };
1555
1556 // Check supercritical pressure
1557 if (_p > psat_max) {
1558 _Q = 1e9;
1559 switch (other) {
1560 case iT: {
1561 if (_T > T_crit_) {
1563 return;
1564 } else {
1566 return;
1567 }
1568 }
1569 case iDmolar: {
1570 if (_rhomolar < rhomolar_crit_) {
1572 return;
1573 } else {
1575 return;
1576 }
1577 }
1578 case iSmolar: {
1579 if (_smolar.pt() > smolar_critical()) {
1581 return;
1582 } else {
1584 return;
1585 }
1586 }
1587 case iHmolar: {
1588 if (_hmolar.pt() > hmolar_critical()) {
1590 return;
1591 } else {
1593 return;
1594 }
1595 }
1596 case iUmolar: {
1597 if (_umolar.pt() > umolar_critical()) {
1599 return;
1600 } else {
1602 return;
1603 }
1604 }
1605 default: {
1606 throw ValueError("supercritical pressure but other invalid for now");
1607 }
1608 }
1609 }
1610 // Check between triple point pressure and psat_max
1611 else if (_p >= components[0].EOS().ptriple * 0.9999 && _p <= psat_max) {
1612
1613 // First try the superancillaries, use them to determine the state if you can
1614 if (get_config_bool(ENABLE_SUPERANCILLARIES) && is_pure()) {
1615 auto& optsuperanc = get_superanc_optional();
1616 // Superancillaries are enabled and available, they will be used to determine the phase
1617 if (optsuperanc) {
1618 auto& superanc = optsuperanc.value();
1619 CoolPropDbl pmax_num = superanc.get_pmax();
1620 if (_p > pmax_num) {
1621 throw ValueError(
1622 format("Pressure to PQ_flash [%0.8Lg Pa] may not be above the numerical critical point of %0.15Lg Pa", _p, pmax_num));
1623 }
1624 auto Tsat = superanc.get_T_from_p(_p);
1625 auto rhoL = superanc.eval_sat(Tsat, 'D', 0);
1626 auto rhoV = superanc.eval_sat(Tsat, 'D', 1);
1627 auto psat = _p;
1628
1629 if (other == iT) {
1630 if (value < Tsat - 100 * DBL_EPSILON) {
1631 this->_phase = iphase_liquid;
1632 _Q = -1000;
1633 return;
1634 } else if (value > Tsat + 100 * DBL_EPSILON) {
1635 this->_phase = iphase_gas;
1636 _Q = 1000;
1637 return;
1638 } else {
1639 this->_phase = iphase_twophase;
1640 }
1641 }
1642 SatL->update_TDmolarP_unchecked(Tsat, rhoL, psat);
1643 SatV->update_TDmolarP_unchecked(Tsat, rhoV, psat);
1644 double Q;
1645 switch (other) {
1646 case iDmolar:
1647 Q = (1 / value - 1 / SatL->rhomolar()) / (1 / SatV->rhomolar() - 1 / SatL->rhomolar());
1648 break;
1649 case iSmolar:
1650 Q = (value - SatL->smolar()) / (SatV->smolar() - SatL->smolar());
1651 break;
1652 case iHmolar:
1653 Q = (value - SatL->hmolar()) / (SatV->hmolar() - SatL->hmolar());
1654 break;
1655 case iUmolar:
1656 Q = (value - SatL->umolar()) / (SatV->umolar() - SatL->umolar());
1657 break;
1658 default:
1659 throw ValueError(format("bad input for other"));
1660 }
1661 // Start off by setting variables based on assumption that the state is two-phase
1662 if (Q < -1e-9) {
1663 this->_phase = iphase_liquid;
1664 SatL->clear();
1665 SatV->clear();
1666 _Q = -1000;
1667 _TLanc = Tsat;
1668 } else if (Q > 1 + 1e-9) {
1669 this->_phase = iphase_gas;
1670 SatL->clear();
1671 SatV->clear();
1672 _Q = 1000;
1673 } else {
1674 _T = Tsat;
1675 _Q = Q;
1676 _rhomolar = 1 / (_Q / SatV->rhomolar() + (1 - _Q) / SatL->rhomolar());
1677 this->_phase = iphase_twophase;
1678 }
1679 return;
1680 }
1681 }
1682
1683 // Then fall back to normal ancillaries, use them to determine the state if you can
1684
1685 // Calculate dew and bubble temps from the ancillaries (everything needs them)
1686 _TLanc = components[0].ancillaries.pL.invert(_p);
1687 _TVanc = components[0].ancillaries.pV.invert(_p);
1688
1689 bool definitely_two_phase = false;
1690
1691 // Try using the ancillaries for P,H,S if they are there
1692 switch (other) {
1693 case iT: {
1694
1695 if (has_melting_line()) {
1696 double Tm = melting_line(iT, iP, _p);
1697 if (get_config_bool(DONT_CHECK_PROPERTY_LIMITS)) {
1699 } else {
1700 if (_T < Tm - 0.001) {
1701 throw ValueError(format("For now, we don't support T [%g K] below Tmelt(p) [%g K]", _T, Tm));
1702 }
1703 }
1704 } else {
1705 if (get_config_bool(DONT_CHECK_PROPERTY_LIMITS)) {
1707 } else {
1708 if (_T < Tmin() - 0.001) {
1709 throw ValueError(format("For now, we don't support T [%g K] below Tmin(saturation) [%g K]", _T, Tmin()));
1710 }
1711 }
1712 }
1713
1714 CoolPropDbl T_vap = 0.1 + static_cast<double>(_TVanc);
1715 CoolPropDbl T_liq = -0.1 + static_cast<double>(_TLanc);
1716
1717 if (value > T_vap) {
1718 this->_phase = iphase_gas;
1719 _Q = -1000;
1720 return;
1721 } else if (value < T_liq) {
1722 this->_phase = iphase_liquid;
1723 _Q = 1000;
1724 return;
1725 }
1726 break;
1727 }
1728 case iHmolar: {
1729 if (!component.ancillaries.hL.enabled()) {
1730 break;
1731 }
1732 // Ancillaries are h-h_anchor, so add back h_anchor
1733 CoolPropDbl h_liq = component.ancillaries.hL.evaluate(_TLanc) + component.EOS().hs_anchor.hmolar;
1734 CoolPropDbl h_liq_error_band = component.ancillaries.hL.get_max_abs_error();
1735 CoolPropDbl h_vap = h_liq + component.ancillaries.hLV.evaluate(_TLanc);
1736 CoolPropDbl h_vap_error_band = h_liq_error_band + component.ancillaries.hLV.get_max_abs_error();
1737
1738 // HelmholtzEOSMixtureBackend HEOS(components);
1739 // HEOS.update(QT_INPUTS, 1, _TLanc);
1740 // double h1 = HEOS.hmolar();
1741 // HEOS.update(QT_INPUTS, 0, _TLanc);
1742 // double h0 = HEOS.hmolar();
1743
1744 // Check if in range given the accuracy of the fit
1745 if (value > h_vap + h_vap_error_band) {
1746 this->_phase = iphase_gas;
1747 _Q = -1000;
1748 return;
1749 } else if (value < h_liq - h_liq_error_band) {
1750 this->_phase = iphase_liquid;
1751 _Q = 1000;
1752 return;
1753 } else if (value > h_liq + h_liq_error_band && value < h_vap - h_vap_error_band) {
1754 definitely_two_phase = true;
1755 }
1756 break;
1757 }
1758 case iSmolar: {
1759 if (!component.ancillaries.sL.enabled()) {
1760 break;
1761 }
1762 // Ancillaries are s-s_anchor, so add back s_anchor
1763 CoolPropDbl s_anchor = component.EOS().hs_anchor.smolar;
1764 CoolPropDbl s_liq = component.ancillaries.sL.evaluate(_TLanc) + s_anchor;
1765 CoolPropDbl s_liq_error_band = component.ancillaries.sL.get_max_abs_error();
1766 CoolPropDbl s_vap = s_liq + component.ancillaries.sLV.evaluate(_TVanc);
1767 CoolPropDbl s_vap_error_band = s_liq_error_band + component.ancillaries.sLV.get_max_abs_error();
1768
1769 // Check if in range given the accuracy of the fit
1770 if (value > s_vap + s_vap_error_band) {
1771 this->_phase = iphase_gas;
1772 _Q = -1000;
1773 return;
1774 } else if (value < s_liq - s_liq_error_band) {
1775 this->_phase = iphase_liquid;
1776 _Q = 1000;
1777 return;
1778 } else if (value > s_liq + s_liq_error_band && value < s_vap - s_vap_error_band) {
1779 definitely_two_phase = true;
1780 }
1781 break;
1782 }
1783 case iUmolar: {
1784 if (!component.ancillaries.hL.enabled()) {
1785 break;
1786 }
1787 // u = h-p/rho
1788
1789 // Ancillaries are h-h_anchor, so add back h_anchor
1790 CoolPropDbl h_liq = component.ancillaries.hL.evaluate(_TLanc) + component.EOS().hs_anchor.hmolar;
1791 CoolPropDbl h_liq_error_band = component.ancillaries.hL.get_max_abs_error();
1792 CoolPropDbl h_vap = h_liq + component.ancillaries.hLV.evaluate(_TLanc);
1793 CoolPropDbl h_vap_error_band = h_liq_error_band + component.ancillaries.hLV.get_max_abs_error();
1794 CoolPropDbl rho_vap = component.ancillaries.rhoV.evaluate(_TVanc);
1795 CoolPropDbl rho_liq = component.ancillaries.rhoL.evaluate(_TLanc);
1796 CoolPropDbl u_liq = h_liq - _p / rho_liq;
1797 CoolPropDbl u_vap = h_vap - _p / rho_vap;
1798 CoolPropDbl u_liq_error_band = 1.5 * h_liq_error_band; // Most of error is in enthalpy
1799 CoolPropDbl u_vap_error_band = 1.5 * h_vap_error_band; // Most of error is in enthalpy
1800
1801 // Check if in range given the accuracy of the fit
1802 if (value > u_vap + u_vap_error_band) {
1803 this->_phase = iphase_gas;
1804 _Q = -1000;
1805 return;
1806 } else if (value < u_liq - u_liq_error_band) {
1807 this->_phase = iphase_liquid;
1808 _Q = 1000;
1809 return;
1810 } else if (value > u_liq + u_liq_error_band && value < u_vap - u_vap_error_band) {
1811 definitely_two_phase = true;
1812 }
1813 break;
1814 }
1815 default: {
1816 }
1817 }
1818
1819 // Now either density is an input, or an ancillary for h,s,u is missing
1820 // Always calculate the densities using the ancillaries
1821 if (!definitely_two_phase) {
1824 CoolPropDbl rho_vap = 0.95 * static_cast<double>(_rhoVanc);
1825 CoolPropDbl rho_liq = 1.05 * static_cast<double>(_rhoLanc);
1826 switch (other) {
1827 case iDmolar: {
1828 if (value < rho_vap) {
1829 this->_phase = iphase_gas;
1830 return;
1831 } else if (value > rho_liq) {
1832 this->_phase = iphase_liquid;
1833 return;
1834 }
1835 break;
1836 }
1837 }
1838 }
1839
1840 if (!is_pure_or_pseudopure) {
1841 throw ValueError("possibly two-phase inputs not supported for mixtures for now");
1842 }
1843
1844 // The slow full VLE calculation is required
1845 {
1846
1847 // Actually have to use saturation information sadly
1848 // For the given pressure, find the saturation state
1849 // Run the saturation routines to determine the saturation densities and pressures
1851 HEOS._p = this->_p;
1852 HEOS._Q = 0; // ?? What is the best to do here? Doesn't matter for our purposes since pure fluid
1854
1855 // We called the saturation routines, so HEOS.SatL and HEOS.SatV are now updated
1856 // with the saturated liquid and vapor values, which can therefore be used in
1857 // the other solvers
1858 saturation_called = true;
1859
1860 CoolPropDbl Q;
1861
1862 if (other == iT) {
1863 if (value < HEOS.SatL->T() - 100 * DBL_EPSILON) {
1864 this->_phase = iphase_liquid;
1865 _Q = -1000;
1866 return;
1867 } else if (value > HEOS.SatV->T() + 100 * DBL_EPSILON) {
1868 this->_phase = iphase_gas;
1869 _Q = 1000;
1870 return;
1871 } else {
1872 this->_phase = iphase_twophase;
1873 }
1874 }
1875 switch (other) {
1876 case iDmolar:
1877 Q = (1 / value - 1 / HEOS.SatL->rhomolar()) / (1 / HEOS.SatV->rhomolar() - 1 / HEOS.SatL->rhomolar());
1878 break;
1879 case iSmolar:
1880 Q = (value - HEOS.SatL->smolar()) / (HEOS.SatV->smolar() - HEOS.SatL->smolar());
1881 break;
1882 case iHmolar:
1883 Q = (value - HEOS.SatL->hmolar()) / (HEOS.SatV->hmolar() - HEOS.SatL->hmolar());
1884 break;
1885 case iUmolar:
1886 Q = (value - HEOS.SatL->umolar()) / (HEOS.SatV->umolar() - HEOS.SatL->umolar());
1887 break;
1888 default:
1889 throw ValueError(format("bad input for other"));
1890 }
1891 // TODO: Check the speed penalty of these calls
1892 // Update the states
1893 if (this->SatL) this->SatL->update(DmolarT_INPUTS, HEOS.SatL->rhomolar(), HEOS.SatL->T());
1894 if (this->SatV) this->SatV->update(DmolarT_INPUTS, HEOS.SatV->rhomolar(), HEOS.SatV->T());
1895 // Update the two-Phase variables
1896 _rhoLmolar = HEOS.SatL->rhomolar();
1897 _rhoVmolar = HEOS.SatV->rhomolar();
1898
1899 //
1900 if (Q < -1e-9) {
1901 this->_phase = iphase_liquid;
1902 _Q = -1000;
1903 return;
1904 } else if (Q > 1 + 1e-9) {
1905 this->_phase = iphase_gas;
1906 _Q = 1000;
1907 return;
1908 } else {
1909 this->_phase = iphase_twophase;
1910 }
1911
1912 _Q = Q;
1913 // Load the outputs
1914 _T = _Q * HEOS.SatV->T() + (1 - _Q) * HEOS.SatL->T();
1915 _rhomolar = 1 / (_Q / HEOS.SatV->rhomolar() + (1 - _Q) / HEOS.SatL->rhomolar());
1916 return;
1917 }
1918 } else if (_p < components[0].EOS().ptriple * 0.9999) {
1919 if (other == iT) {
1920 if (_T > std::max(Tmin(), Ttriple())) {
1922 } else {
1923 if (get_config_bool(DONT_CHECK_PROPERTY_LIMITS)) {
1925 } else {
1926 throw NotImplementedError(format("For now, we don't support p [%g Pa] below ptriple [%g Pa] when T [%g] is less than Tmin [%g]",
1927 _p, components[0].EOS().ptriple, _T, std::max(Tmin(), Ttriple())));
1928 }
1929 }
1930 } else {
1932 }
1933 } else {
1934 throw ValueError(format("The pressure [%g Pa] cannot be used in p_phase_determination", _p));
1935 }
1936}
1938 class Residual : public FuncWrapper1D
1939 {
1940 public:
1942 Residual(HelmholtzEOSMixtureBackend& HEOS) : HEOS(&HEOS) {};
1943 double call(double T) {
1944 HEOS->update(QT_INPUTS, 1, T);
1945 // dTdp_along_sat
1946 double dTdp_along_sat =
1947 HEOS->T() * (1 / HEOS->SatV->rhomolar() - 1 / HEOS->SatL->rhomolar()) / (HEOS->SatV->hmolar() - HEOS->SatL->hmolar());
1948 // dsdT_along_sat;
1949 return HEOS->SatV->first_partial_deriv(iSmolar, iT, iP) + HEOS->SatV->first_partial_deriv(iSmolar, iP, iT) / dTdp_along_sat;
1950 }
1951 };
1953 shared_ptr<CoolProp::HelmholtzEOSMixtureBackend> HEOS_copy(new CoolProp::HelmholtzEOSMixtureBackend(get_components()));
1954 Residual resid(*HEOS_copy);
1955 const CoolProp::SimpleState& tripleV = HEOS_copy->get_components()[0].triple_vapor;
1956 double v1 = resid.call(hsat_max.T);
1957 double v2 = resid.call(tripleV.T);
1958 // If there is a sign change, there is a maxima, otherwise there is no local maxima/minima
1959 if (v1 * v2 < 0) {
1960 Brent(resid, hsat_max.T, tripleV.T, DBL_EPSILON, 1e-8, 30);
1961 ssat_max.T = resid.HEOS->T();
1962 ssat_max.p = resid.HEOS->p();
1963 ssat_max.rhomolar = resid.HEOS->rhomolar();
1964 ssat_max.hmolar = resid.HEOS->hmolar();
1965 ssat_max.smolar = resid.HEOS->smolar();
1967 } else {
1969 }
1970 }
1971}
1973 class Residualhmax : public FuncWrapper1D
1974 {
1975 public:
1977 Residualhmax(HelmholtzEOSMixtureBackend& HEOS) : HEOS(&HEOS) {};
1978 double call(double T) {
1979 HEOS->update(QT_INPUTS, 1, T);
1980 // dTdp_along_sat
1981 double dTdp_along_sat =
1982 HEOS->T() * (1 / HEOS->SatV->rhomolar() - 1 / HEOS->SatL->rhomolar()) / (HEOS->SatV->hmolar() - HEOS->SatL->hmolar());
1983 // dhdT_along_sat;
1984 return HEOS->SatV->first_partial_deriv(iHmolar, iT, iP) + HEOS->SatV->first_partial_deriv(iHmolar, iP, iT) / dTdp_along_sat;
1985 }
1986 };
1987 if (!hsat_max.is_valid()) {
1988 shared_ptr<CoolProp::HelmholtzEOSMixtureBackend> HEOS_copy(new CoolProp::HelmholtzEOSMixtureBackend(get_components()));
1989 Residualhmax residhmax(*HEOS_copy);
1990 Brent(residhmax, T_critical() - 0.1, HEOS_copy->Ttriple() + 1, DBL_EPSILON, 1e-8, 30);
1991 hsat_max.T = residhmax.HEOS->T();
1992 hsat_max.p = residhmax.HEOS->p();
1993 hsat_max.rhomolar = residhmax.HEOS->rhomolar();
1994 hsat_max.hmolar = residhmax.HEOS->hmolar();
1995 hsat_max.smolar = residhmax.HEOS->smolar();
1996 }
1997}
1999 if (!ValidNumber(value)) {
2000 throw ValueError(format("value to T_phase_determination_pure_or_pseudopure is invalid"));
2001 };
2002
2003 double T_crit_ = T_critical(), p_crit_ = p_critical(), rhomolar_crit_ = rhomolar_critical();
2004 auto smolar_critical = [this, &T_crit_, &rhomolar_crit_]() { return this->calc_smolar_nocache(T_crit_, rhomolar_crit_); };
2005 auto hmolar_critical = [this, &T_crit_, &rhomolar_crit_]() { return this->calc_hmolar_nocache(T_crit_, rhomolar_crit_); };
2006 auto umolar_critical = [this, &T_crit_, &rhomolar_crit_]() { return this->calc_umolar_nocache(T_crit_, rhomolar_crit_); };
2007
2008 // T is known, another input P, T, H, S, U is given (all molar)
2009 if (_T < T_crit_ && _p > p_crit_) {
2010 // Only ever true if (other = iP); otherwise _p = -HUGE
2012 } else if (std::abs(_T - T_crit_) < 10 * DBL_EPSILON) // Exactly at Tcrit
2013 {
2014 switch (other) {
2015 case iDmolar:
2016 if (std::abs(_rhomolar - rhomolar_crit_) < 10 * DBL_EPSILON) {
2018 break;
2019 } else if (_rhomolar > rhomolar_crit_) {
2021 break;
2022 } else {
2024 break;
2025 }
2026 case iP: {
2027 if (std::abs(_p - p_crit_) < 10 * DBL_EPSILON) {
2029 break;
2030 } else if (_p > p_crit_) {
2032 break;
2033 } else {
2035 break;
2036 }
2037 }
2038 default:
2039 throw ValueError(format("T=Tcrit; invalid input for other to T_phase_determination_pure_or_pseudopure"));
2040 }
2041 } else if (_T < T_crit_) // Gas, 2-Phase, Liquid, or Supercritical Liquid Region
2042 {
2043 if (get_config_bool(ENABLE_SUPERANCILLARIES) && is_pure()) {
2044 auto& optsuperanc = get_superanc_optional();
2045 // Superancillaries are enabled and available, they will be used to determine the phase
2046 if (optsuperanc) {
2047 auto& superanc = optsuperanc.value();
2048 auto rhoL = superanc.eval_sat(_T, 'D', 0);
2049 auto rhoV = superanc.eval_sat(_T, 'D', 1);
2050 auto psat = superanc.eval_sat(_T, 'P', 1);
2051 _rhoLanc = rhoL;
2052 _rhoVanc = rhoV;
2053 _rhoLmolar = rhoL;
2054 _rhoVmolar = rhoV;
2055
2056 if (other == iP) {
2057 if (std::abs(psat / value - 1) < 1e-6) {
2058 throw ValueError(
2059 format("Saturation pressure [%g Pa] corresponding to T [%g K] is within 1e-4 %% of given p [%Lg Pa]", psat, _T, value));
2060 } else if (value < psat) {
2062 _Q = -1000;
2063 } else if (value > psat) {
2065 _Q = 1000;
2066 }
2067 return;
2068 }
2069 double Q = -1;
2070 if (other == iDmolar) {
2071 // Special case density as an input
2072 Q = (1 / value - 1 / rhoL) / (1 / rhoV - 1 / rhoL);
2073 if (Q <= 0) {
2075 _Q = -1000;
2076 } else if (Q >= 1) {
2078 _Q = 1000;
2079 } else {
2081 _p = psat;
2082 this->_Q = Q;
2083 SatL->update(DmolarT_INPUTS, rhoL, _T);
2084 SatV->update(DmolarT_INPUTS, rhoV, _T);
2085 }
2086 _rhomolar = value;
2087 return;
2088 }
2089
2090 SatL->update_TDmolarP_unchecked(_T, rhoL, psat);
2091 SatV->update_TDmolarP_unchecked(_T, rhoV, psat);
2092
2093 switch (other) {
2094 case iDmolar:
2095 Q = (1 / value - 1 / SatL->rhomolar()) / (1 / SatV->rhomolar() - 1 / SatL->rhomolar());
2096 break;
2097 case iSmolar:
2098 Q = (value - SatL->smolar()) / (SatV->smolar() - SatL->smolar());
2099 break;
2100 case iHmolar:
2101 Q = (value - SatL->hmolar()) / (SatV->hmolar() - SatL->hmolar());
2102 break;
2103 case iUmolar:
2104 Q = (value - SatL->umolar()) / (SatV->umolar() - SatL->umolar());
2105 break;
2106 default:
2107 throw ValueError(format("bad input for other"));
2108 }
2109
2110 if (Q < 0) {
2111 this->_phase = iphase_liquid;
2112 SatL->clear();
2113 SatV->clear();
2114 _Q = -1000;
2115
2116 } else if (Q > 1) {
2117 this->_phase = iphase_gas;
2118 SatL->clear();
2119 SatV->clear();
2120 _Q = 1000;
2121 } else {
2123 _p = psat;
2124 _Q = Q;
2125 _rhomolar = 1 / (_Q / rhoV + (1 - _Q) / rhoL);
2126 }
2127 return;
2128 }
2129 }
2130
2131 // Start to think about the saturation stuff
2132 // First try to use the ancillary equations if you are far enough away
2133 // You know how accurate the ancillary equations are thanks to using CoolProp code to refit them
2134 switch (other) {
2135 case iP: {
2136 _pLanc = components[0].ancillaries.pL.evaluate(_T);
2137 _pVanc = components[0].ancillaries.pV.evaluate(_T);
2138 CoolPropDbl p_vap = 0.98 * static_cast<double>(_pVanc);
2139 CoolPropDbl p_liq = 1.02 * static_cast<double>(_pLanc);
2140
2141 if (value < p_vap) {
2142 this->_phase = iphase_gas;
2143 _Q = -1000;
2144 return;
2145 } else if (value > p_liq) {
2146 this->_phase = iphase_liquid;
2147 _Q = 1000;
2148 return;
2149 } else if (!is_pure()) // pseudo-pure
2150 {
2151 // For pseudo-pure fluids, the ancillary pressure curves are the official
2152 // arbiter of the phase
2153 if (value > static_cast<CoolPropDbl>(_pLanc)) {
2154 this->_phase = iphase_liquid;
2155 _Q = 1000;
2156 return;
2157 } else if (value < static_cast<CoolPropDbl>(_pVanc)) {
2158 this->_phase = iphase_gas;
2159 _Q = -1000;
2160 return;
2161 } else {
2162 throw ValueError("Two-phase inputs not supported for pseudo-pure for now");
2163 }
2164 }
2165 break;
2166 }
2167 default: {
2168 // Always calculate the densities using the ancillaries
2169 _rhoVanc = components[0].ancillaries.rhoV.evaluate(_T);
2170 _rhoLanc = components[0].ancillaries.rhoL.evaluate(_T);
2171 CoolPropDbl rho_vap = 0.95 * static_cast<double>(_rhoVanc);
2172 CoolPropDbl rho_liq = 1.05 * static_cast<double>(_rhoLanc);
2173 switch (other) {
2174 case iDmolar: {
2175 if (value < rho_vap) {
2176 this->_phase = iphase_gas;
2177 return;
2178 } else if (value > rho_liq) {
2179 this->_phase = iphase_liquid;
2180 return;
2181 } else {
2182 // Next we check the vapor quality based on the ancillary values
2183 double Qanc = (1 / value - 1 / static_cast<double>(_rhoLanc))
2184 / (1 / static_cast<double>(_rhoVanc) - 1 / static_cast<double>(_rhoLanc));
2185 // If the vapor quality is significantly inside the two-phase zone, stop, we are definitely two-phase
2186 if (value > 0.95 * rho_liq || value < 1.05 * rho_vap) {
2187 // Definitely single-phase
2188 _phase = iphase_liquid; // Needed for direct update call
2189 _Q = -1000; // Needed for direct update call
2190 update_DmolarT_direct(value, _T);
2191 CoolPropDbl pL = components[0].ancillaries.pL.evaluate(_T);
2192 if (Qanc < 0.01 && _p > pL * 1.05 && first_partial_deriv(iP, iDmolar, iT) > 0
2195 _Q = -1000;
2196 return;
2197 } else if (Qanc > 1.01) {
2198 break;
2199 } else {
2201 _p = _HUGE;
2202 }
2203 }
2204 }
2205 break;
2206 }
2207 default: {
2208 if (!this->SatL || !this->SatV) {
2209 throw ValueError(format("The saturation properties are needed in T_phase_determination_pure_or_pseudopure"));
2210 }
2211 // If it is not density, update the states
2212 SatV->update(DmolarT_INPUTS, rho_vap, _T);
2213 SatL->update(DmolarT_INPUTS, rho_liq, _T);
2214
2215 // First we check ancillaries
2216 switch (other) {
2217 case iSmolar: {
2218 if (value > SatV->calc_smolar()) {
2219 this->_phase = iphase_gas;
2220 return;
2221 }
2222 if (value < SatL->calc_smolar()) {
2223 this->_phase = iphase_liquid;
2224 return;
2225 }
2226 break;
2227 }
2228 case iHmolar: {
2229 if (value > SatV->calc_hmolar()) {
2230 this->_phase = iphase_gas;
2231 return;
2232 } else if (value < SatL->calc_hmolar()) {
2233 this->_phase = iphase_liquid;
2234 return;
2235 }
2236 break;
2237 }
2238 case iUmolar: {
2239 if (value > SatV->calc_umolar()) {
2240 this->_phase = iphase_gas;
2241 return;
2242 } else if (value < SatL->calc_umolar()) {
2243 this->_phase = iphase_liquid;
2244 return;
2245 }
2246 break;
2247 }
2248 default:
2249 throw ValueError(format("invalid input for other to T_phase_determination_pure_or_pseudopure"));
2250 }
2251 }
2252 }
2253 }
2254 }
2255
2256 // Actually have to use saturation information sadly
2257 // For the given temperature, find the saturation state
2258 // Run the saturation routines to determine the saturation densities and pressures
2262
2263 CoolPropDbl Q;
2264
2265 if (other == iP) {
2266 if (value > HEOS.SatL->p() * (1e-6 + 1)) {
2267 this->_phase = iphase_liquid;
2268 _Q = -1000;
2269 return;
2270 } else if (value < HEOS.SatV->p() * (1 - 1e-6)) {
2271 this->_phase = iphase_gas;
2272 _Q = 1000;
2273 return;
2274 } else {
2275 throw ValueError(
2276 format("Saturation pressure [%g Pa] corresponding to T [%g K] is within 1e-4 %% of given p [%Lg Pa]", HEOS.SatL->p(), _T, value));
2277 }
2278 }
2279
2280 switch (other) {
2281 case iDmolar:
2282 Q = (1 / value - 1 / HEOS.SatL->rhomolar()) / (1 / HEOS.SatV->rhomolar() - 1 / HEOS.SatL->rhomolar());
2283 break;
2284 case iSmolar:
2285 Q = (value - HEOS.SatL->smolar()) / (HEOS.SatV->smolar() - HEOS.SatL->smolar());
2286 break;
2287 case iHmolar:
2288 Q = (value - HEOS.SatL->hmolar()) / (HEOS.SatV->hmolar() - HEOS.SatL->hmolar());
2289 break;
2290 case iUmolar:
2291 Q = (value - HEOS.SatL->umolar()) / (HEOS.SatV->umolar() - HEOS.SatL->umolar());
2292 break;
2293 default:
2294 throw ValueError(format("bad input for other"));
2295 }
2296
2297 // Update the states
2298 if (this->SatL) this->SatL->update(DmolarT_INPUTS, HEOS.SatL->rhomolar(), HEOS.SatL->T());
2299 if (this->SatV) this->SatV->update(DmolarT_INPUTS, HEOS.SatV->rhomolar(), HEOS.SatV->T());
2300 // Update the two-Phase variables
2301 _rhoLmolar = HEOS.SatL->rhomolar();
2302 _rhoVmolar = HEOS.SatV->rhomolar();
2303
2304 if (Q < 0) {
2305 this->_phase = iphase_liquid;
2306 _Q = -1;
2307 return;
2308 } else if (Q > 1) {
2309 this->_phase = iphase_gas;
2310 _Q = 1;
2311 return;
2312 } else {
2313 this->_phase = iphase_twophase;
2314 }
2315 _Q = Q;
2316 // Load the outputs
2317 _p = _Q * HEOS.SatV->p() + (1 - _Q) * HEOS.SatL->p();
2318 _rhomolar = 1 / (_Q / HEOS.SatV->rhomolar() + (1 - _Q) / HEOS.SatL->rhomolar());
2319 return;
2320 } else if (_T > T_crit_ && _T > components[0].EOS().Ttriple) // Supercritical or Supercritical Gas Region
2321 {
2322 _Q = 1e9;
2323 switch (other) {
2324 case iP: {
2325 if (_p > p_crit_) {
2327 return;
2328 } else {
2330 return;
2331 }
2332 }
2333 case iDmolar: {
2334 if (_rhomolar > rhomolar_crit_) {
2336 return;
2337 } else {
2339 return;
2340 }
2341 }
2342 case iSmolar: {
2343 if (_smolar.pt() > smolar_critical()) {
2345 return;
2346 } else {
2348 return;
2349 }
2350 }
2351 case iHmolar: {
2352 if (_hmolar.pt() > hmolar_critical()) {
2354 return;
2355 } else {
2357 return;
2358 }
2359 }
2360 case iUmolar: {
2361 if (_umolar.pt() > umolar_critical()) {
2363 return;
2364 } else {
2366 return;
2367 }
2368 }
2369 default: {
2370 throw ValueError("supercritical temp but other invalid for now");
2371 }
2372 }
2373 } else {
2374 throw ValueError(format("For now, we don't support T [%g K] below Ttriple [%g K]", _T, components[0].EOS().Ttriple));
2375 }
2376}
2378 CoolPropDbl T = HEOS->T(), rho = HEOS->rhomolar(), rhor = HEOS->get_reducing_state().rhomolar, Tr = HEOS->get_reducing_state().T,
2379 dT_dtau = -pow(T, 2) / Tr, R = HEOS->gas_constant(), delta = rho / rhor, tau = Tr / T;
2380
2381 switch (index) {
2382 case iT:
2383 dT = 1;
2384 drho = 0;
2385 break;
2386 case iDmolar:
2387 dT = 0;
2388 drho = 1;
2389 break;
2390 case iDmass:
2391 dT = 0;
2392 drho = HEOS->molar_mass();
2393 break;
2394 case iP: {
2395 // dp/drho|T
2396 drho = R * T * (1 + 2 * delta * HEOS->dalphar_dDelta() + pow(delta, 2) * HEOS->d2alphar_dDelta2());
2397 // dp/dT|rho
2398 dT = rho * R * (1 + delta * HEOS->dalphar_dDelta() - tau * delta * HEOS->d2alphar_dDelta_dTau());
2399 break;
2400 }
2401 case iHmass:
2402 case iHmolar: {
2403 // dh/dT|rho
2404 dT = R
2405 * (-pow(tau, 2) * (HEOS->d2alpha0_dTau2() + HEOS->d2alphar_dTau2())
2406 + (1 + delta * HEOS->dalphar_dDelta() - tau * delta * HEOS->d2alphar_dDelta_dTau()));
2407 // dh/drhomolar|T
2408 drho =
2409 T * R / rho * (tau * delta * HEOS->d2alphar_dDelta_dTau() + delta * HEOS->dalphar_dDelta() + pow(delta, 2) * HEOS->d2alphar_dDelta2());
2410 if (index == iHmass) {
2411 // dhmolar/drhomolar|T * dhmass/dhmolar where dhmass/dhmolar = 1/mole mass
2412 drho /= HEOS->molar_mass();
2413 dT /= HEOS->molar_mass();
2414 }
2415 break;
2416 }
2417 case iSmass:
2418 case iSmolar: {
2419 // ds/dT|rho
2420 dT = R / T * (-pow(tau, 2) * (HEOS->d2alpha0_dTau2() + HEOS->d2alphar_dTau2()));
2421 // ds/drho|T
2422 drho = R / rho * (-(1 + delta * HEOS->dalphar_dDelta() - tau * delta * HEOS->d2alphar_dDelta_dTau()));
2423 if (index == iSmass) {
2424 // ds/drho|T / drhomass/drhomolar where drhomass/drhomolar = mole mass
2425 drho /= HEOS->molar_mass();
2426 dT /= HEOS->molar_mass();
2427 }
2428 break;
2429 }
2430 case iUmass:
2431 case iUmolar: {
2432 // du/dT|rho
2433 dT = R * (-pow(tau, 2) * (HEOS->d2alpha0_dTau2() + HEOS->d2alphar_dTau2()));
2434 // du/drho|T
2435 drho = HEOS->T() * R / rho * (tau * delta * HEOS->d2alphar_dDelta_dTau());
2436 if (index == iUmass) {
2437 // du/drho|T / drhomass/drhomolar where drhomass/drhomolar = mole mass
2438 drho /= HEOS->molar_mass();
2439 dT /= HEOS->molar_mass();
2440 }
2441 break;
2442 }
2443 case iTau:
2444 dT = 1 / dT_dtau;
2445 drho = 0;
2446 break;
2447 case iDelta:
2448 dT = 0;
2449 drho = 1 / rhor;
2450 break;
2451 default:
2452 throw ValueError(format("input to get_dT_drho[%s] is invalid", get_parameter_information(index, "short").c_str()));
2453 }
2454}
2455
2458 CoolPropDbl delta = rhomolar / reducing.rhomolar;
2459 CoolPropDbl tau = reducing.T / T;
2460
2461 // Calculate derivative
2462 int nTau = 0, nDelta = 1;
2464
2465 // Get pressure
2466 return rhomolar * gas_constant() * T * (1 + delta * dalphar_dDelta);
2467}
2469 CoolPropDbl& light, CoolPropDbl& heavy) {
2470
2472 class dpdrho_resid : public FuncWrapper1DWithTwoDerivs
2473 {
2474 public:
2476 CoolPropDbl T, p, delta, rhor, tau, R_u;
2477
2479 : HEOS(HEOS),
2480 T(T),
2481 p(p),
2482 delta(_HUGE),
2483 rhor(HEOS->get_reducing_state().rhomolar),
2484 tau(HEOS->get_reducing_state().T / T),
2485 R_u(HEOS->gas_constant()) {}
2486 double call(double rhomolar) {
2487 delta = rhomolar / rhor; // needed for derivative
2489 // dp/drho|T
2490 return R_u * T * (1 + 2 * delta * HEOS->dalphar_dDelta() + POW2(delta) * HEOS->d2alphar_dDelta2());
2491 };
2492 double deriv(double rhomolar) {
2493 // d2p/drho2|T
2494 return R_u * T / rhor * (2 * HEOS->dalphar_dDelta() + 4 * delta * HEOS->d2alphar_dDelta2() + POW2(delta) * HEOS->calc_d3alphar_dDelta3());
2495 };
2496 double second_deriv(double rhomolar) {
2497 // d3p/drho3|T
2498 return R_u * T / POW2(rhor)
2499 * (6 * HEOS->d2alphar_dDelta2() + 6 * delta * HEOS->d3alphar_dDelta3() + POW2(delta) * HEOS->calc_d4alphar_dDelta4());
2500 };
2501 };
2502 dpdrho_resid resid(this, T, p);
2503 light = -1;
2504 heavy = -1;
2505 try {
2506 light = Halley(resid, 1e-6, 1e-8, 100);
2507 double d2pdrho2__constT = resid.deriv(light);
2508 if (d2pdrho2__constT > 0) {
2509 // Not possible since curvature should be negative
2510 throw CoolProp::ValueError("curvature cannot be positive");
2511 }
2512 } catch (std::exception& e) {
2513 if (get_debug_level() > 5) {
2514 std::cout << e.what() << std::endl;
2515 };
2516 light = -1;
2517 }
2518
2519 if (light < 0) {
2520 try {
2521 // Now we are going to do something VERY slow - increase density until curvature is positive
2522 double rho = 1e-6;
2523 for (std::size_t counter = 0; counter <= 100; counter++) {
2524 resid.call(rho); // Updates the state
2525 double curvature = resid.deriv(rho);
2526 if (curvature > 0) {
2527 light = rho;
2528 break;
2529 }
2530 rho *= 2;
2531 }
2532 } catch (...) {
2533 }
2534 }
2535
2536 // First try a "normal" calculation of the stationary point on the liquid side
2537 for (double omega = 0.7; omega > 0; omega -= 0.2) {
2538 try {
2539 resid.options.add_number("omega", omega);
2540 heavy = Halley(resid, rhomax, 1e-8, 100);
2541 double d2pdrho2__constT = resid.deriv(heavy);
2542 if (d2pdrho2__constT < 0) {
2543 // Not possible since curvature should be positive
2544 throw CoolProp::ValueError("curvature cannot be negative");
2545 }
2546 break; // Jump out, we got a good solution
2547 } catch (std::exception& e) {
2548 if (get_debug_level() > 5) {
2549 std::cout << e.what() << std::endl;
2550 };
2551 heavy = -1;
2552 }
2553 }
2554
2555 if (heavy < 0) {
2556 try {
2557 // Now we are going to do something VERY slow - decrease density until curvature is negative or pressure is negative
2558 double rho = rhomax;
2559 for (std::size_t counter = 0; counter <= 100; counter++) {
2560 resid.call(rho); // Updates the state
2561 double curvature = resid.deriv(rho);
2562 if (curvature < 0 || this->p() < 0) {
2563 heavy = rho;
2564 break;
2565 }
2566 rho /= 1.1;
2567 }
2568 } catch (...) {
2569 }
2570 }
2571
2572 if (light > 0 && heavy > 0) {
2573 // Found two stationary points, done!
2575 }
2576 // If no solution is found for dpdrho|T=0 starting at high and low densities,
2577 // then try to do a bounded solver to see if you can find any solutions. If you
2578 // can't, p = f(rho) is probably monotonic (supercritical?), and the bounds are
2579 else if (light < 0 && heavy < 0) {
2580 double dpdrho_min = resid.call(1e-10);
2581 double dpdrho_max = resid.call(rhomax);
2582 if (dpdrho_max * dpdrho_min > 0) {
2584 } else {
2585 throw CoolProp::ValueError("zero stationary points -- does this make sense?");
2586 }
2587 } else {
2589 }
2590}
2591// Define the residual to be driven to zero
2593{
2594 public:
2597
2599 : HEOS(HEOS),
2600 T(T),
2601 p(p),
2602 delta(_HUGE),
2603 rhor(HEOS->get_reducing_state().rhomolar),
2604 tau(HEOS->get_reducing_state().T / T),
2605 R_u(HEOS->gas_constant()) {}
2606 double call(double rhomolar) {
2607 delta = rhomolar / rhor; // needed for derivative
2608 HEOS->update_DmolarT_direct(rhomolar, T);
2609 CoolPropDbl peos = HEOS->p();
2610 return (peos - p) / p;
2611 };
2612 double deriv(double rhomolar) {
2613 // dp/drho|T / pspecified
2614 return R_u * T * (1 + 2 * delta * HEOS->dalphar_dDelta() + POW2(delta) * HEOS->d2alphar_dDelta2()) / p;
2615 };
2616 double second_deriv(double rhomolar) {
2617 // d2p/drho2|T / pspecified
2618 return R_u * T / rhor * (2 * HEOS->dalphar_dDelta() + 4 * delta * HEOS->d2alphar_dDelta2() + POW2(delta) * HEOS->calc_d3alphar_dDelta3()) / p;
2619 };
2620 double third_deriv(double rhomolar) {
2621 // d3p/drho3|T / pspecified
2622 return R_u * T / POW2(rhor)
2624 };
2625};
2627 double b = 0;
2628 for (std::size_t i = 0; i < mole_fractions.size(); ++i) {
2629 // Get the parameters for the cubic EOS
2631 CoolPropDbl R = 8.3144598;
2632 b += mole_fractions[i] * 0.08664 * R * Tc / pc;
2633 }
2634 return b;
2635}
2637 // Find the densities along the isotherm where dpdrho|T = 0 (if you can)
2638 CoolPropDbl light = -1, heavy = -1;
2639 StationaryPointReturnFlag retval = solver_dpdrho0_Tp(T, p, rhomolar_max, light, heavy);
2640
2641 // Define the solver class
2642 SolverTPResid resid(this, T, p);
2643
2644 if (retval == ZERO_STATIONARY_POINTS) {
2645 // It's monotonic (no stationary points found), so do the full bounded solver
2646 // for the density
2647 double rho = Brent(resid, 1e-10, rhomolar_max, DBL_EPSILON, 1e-8, 100);
2648 return rho;
2649 } else if (retval == TWO_STATIONARY_POINTS_FOUND) {
2650
2651 // Calculate the pressures at the min and max densities where dpdrho|T = 0
2652 double p_at_rhomin_stationary = calc_pressure_nocache(T, light);
2653 double p_at_rhomax_stationary = calc_pressure_nocache(T, heavy);
2654
2655 double rho_liq = -1, rho_vap = -1;
2656 if (p > p_at_rhomax_stationary) {
2657 int counter = 0;
2658 for (/* init above, for debugging */; counter <= 10; counter++) {
2659 // Bump up rhomax if needed to bound the given pressure
2660 double p_at_rhomax = calc_pressure_nocache(T, rhomolar_max);
2661 if (p_at_rhomax < p) {
2662 rhomolar_max *= 1.05;
2663 } else {
2664 break;
2665 }
2666 }
2667 // Look for liquid root starting at stationary point density
2668 rho_liq = Brent(resid, heavy, rhomolar_max, DBL_EPSILON, 1e-8, 100);
2669 }
2670
2671 if (p < p_at_rhomin_stationary) {
2672 // Look for vapor root starting at stationary point density
2673 rho_vap = Brent(resid, light, 1e-10, DBL_EPSILON, 1e-8, 100);
2674 }
2675
2676 if (rho_vap > 0 && rho_liq > 0) {
2677 // Both densities are the same
2678 if (std::abs(rho_vap - rho_liq) < 1e-10) {
2679 // return one of them
2680 return rho_vap;
2681 } else {
2682 // Two solutions found, keep the one with lower Gibbs energy
2683 double gibbsmolar_vap = calc_gibbsmolar_nocache(T, rho_vap);
2684 double gibbsmolar_liq = calc_gibbsmolar_nocache(T, rho_liq);
2685 if (gibbsmolar_liq < gibbsmolar_vap) {
2686 return rho_liq;
2687 } else {
2688 return rho_vap;
2689 }
2690 }
2691 } else if (rho_vap < 0 && rho_liq > 0) {
2692 // Liquid root found, return it
2693 return rho_liq;
2694 } else if (rho_vap > 0 && rho_liq < 0) {
2695 // Vapor root found, return it
2696 return rho_vap;
2697 } else {
2698 throw CoolProp::ValueError(format("No density solutions for T=%g,p=%g,z=%s", T, p,
2699 vec_to_string(static_cast<std::vector<double>>(mole_fractions), "%0.12g").c_str()));
2700 }
2701 } else {
2702 throw CoolProp::ValueError(format("One stationary point (not good) for T=%g,p=%g,z=%s", T, p,
2703 vec_to_string(static_cast<std::vector<double>>(mole_fractions), "%0.12g").c_str()));
2704 }
2705};
2706
2708 phases phase;
2709
2710 SolverTPResid resid(this, T, p);
2711
2712 // Check if the phase is imposed
2714 // Use the imposed phase index
2716 else
2717 // Use the phase index in the class
2718 phase = _phase;
2719
2720 if (rhomolar_guess < 0) // Not provided
2721 {
2722 // Calculate a guess value using SRK equation of state
2723 rhomolar_guess = solver_rho_Tp_SRK(T, p, phase);
2724
2725 // A gas-like phase, ideal gas might not be the perfect model, but probably good enough
2727 if (rhomolar_guess < 0 || !ValidNumber(rhomolar_guess)) // If the guess is bad, probably high temperature, use ideal gas
2728 {
2729 rhomolar_guess = p / (gas_constant() * T);
2730 }
2731 } else if (phase == iphase_liquid) {
2732 double rhomolar;
2734 // It's liquid at subcritical pressure, we can use ancillaries as guess value
2735 CoolPropDbl _rhoLancval = static_cast<CoolPropDbl>(components[0].ancillaries.rhoL.evaluate(T));
2736 try {
2737 // First we try with Halley's method starting at saturated liquid
2738 rhomolar = Halley(resid, _rhoLancval, 1e-8, 100);
2741 throw ValueError("Liquid density is invalid");
2742 }
2743 } catch (std::exception&) {
2744 // Next we try with a Brent method bounded solver since the function is 1-1
2745 rhomolar = Brent(resid, _rhoLancval * 0.9, _rhoLancval * 1.3, DBL_EPSILON, 1e-8, 100);
2746 if (!ValidNumber(rhomolar)) {
2747 throw ValueError();
2748 }
2749 }
2750 } else {
2751 // Try with 4th order Householder method starting at a very high density
2752 rhomolar = Householder4(&resid, 3 * rhomolar_reducing(), 1e-8, 100);
2753 }
2754 return rhomolar;
2755 } else if (phase == iphase_supercritical_liquid) {
2756 CoolPropDbl rhoLancval = static_cast<CoolPropDbl>(components[0].ancillaries.rhoL.evaluate(T));
2757 CoolPropDbl rhoLtripleancval = static_cast<CoolPropDbl>(components[0].ancillaries.rhoL.evaluate(Ttriple()));
2758
2759 // Next we try with a Brent method bounded solver since the function should be 1-1 in most cases
2760 // But some EOS have a maximum in pressure so if rhoc*4 is after the maximum in pressure, this method will fail
2761 // and fall back to a narrower range of densities
2762 try {
2763 double rhomolar = Brent(resid, rhoLancval * 0.99, rhomolar_critical() * 4, DBL_EPSILON, 1e-8, 100);
2764 if (!ValidNumber(rhomolar)) {
2765 throw ValueError();
2766 }
2767 return rhomolar;
2768 } catch (...) {
2769 double rhomolar = Brent(resid, rhoLancval * 0.99, rhoLtripleancval * 1.1, DBL_EPSILON, 1e-8, 100);
2770 if (!ValidNumber(rhomolar)) {
2771 throw ValueError();
2772 }
2773 return rhomolar;
2774 }
2775 }
2776 }
2777
2778 try {
2779 double rhomolar = Householder4(resid, rhomolar_guess, 1e-8, 20);
2780 if (!ValidNumber(rhomolar) || rhomolar < 0) {
2781 throw ValueError();
2782 }
2783 if (phase == iphase_liquid) {
2784 double dpdrho = first_partial_deriv(iP, iDmolar, iT);
2785 double d2pdrho2 = second_partial_deriv(iP, iDmolar, iT, iDmolar, iT);
2786 if (dpdrho < 0 || d2pdrho2 < 0) {
2787 // Try again with a larger density in order to end up at the right solution
2788 rhomolar = Householder4(resid, 3 * rhomolar_reducing(), 1e-8, 100);
2789 return rhomolar;
2790 }
2791 } else if (phase == iphase_gas) {
2792 double dpdrho = first_partial_deriv(iP, iDmolar, iT);
2793 double d2pdrho2 = second_partial_deriv(iP, iDmolar, iT, iDmolar, iT);
2794 if (dpdrho < 0 || d2pdrho2 > 0) {
2795 // Try again with a tiny density in order to end up at the right solution
2796 rhomolar = Householder4(resid, 1e-6, 1e-8, 100);
2797 return rhomolar;
2798 }
2799 }
2800 return rhomolar;
2801 } catch (std::exception& e) {
2803 double rhomolar = Brent(resid, 1e-10, 3 * rhomolar_reducing(), DBL_EPSILON, 1e-8, 100);
2804 return rhomolar;
2805 } else if (is_pure_or_pseudopure && T > T_critical()) {
2806 try {
2807 double rhomolar = Brent(resid, 1e-10, 5 * rhomolar_reducing(), DBL_EPSILON, 1e-8, 100);
2808 return rhomolar;
2809
2810 } catch (...) {
2811 double rhomolar = Householder4(resid, 3 * rhomolar_reducing(), 1e-8, 100);
2812 return rhomolar;
2813 }
2814 }
2815 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,
2816 rhomolar_guess, e.what()));
2817 }
2818}
2820 CoolPropDbl rhomolar, R_u = gas_constant(), a = 0, b = 0, k_ij = 0;
2821
2822 for (std::size_t i = 0; i < components.size(); ++i) {
2823 CoolPropDbl Tci = components[i].EOS().reduce.T, pci = components[i].EOS().reduce.p, acentric_i = components[i].EOS().acentric;
2824 CoolPropDbl m_i = 0.480 + 1.574 * acentric_i - 0.176 * pow(acentric_i, 2);
2825 CoolPropDbl b_i = 0.08664 * R_u * Tci / pci;
2826 b += mole_fractions[i] * b_i;
2827
2828 CoolPropDbl a_i = 0.42747 * pow(R_u * Tci, 2) / pci * pow(1 + m_i * (1 - sqrt(T / Tci)), 2);
2829
2830 for (std::size_t j = 0; j < components.size(); ++j) {
2831 CoolPropDbl Tcj = components[j].EOS().reduce.T, pcj = components[j].EOS().reduce.p, acentric_j = components[j].EOS().acentric;
2832 CoolPropDbl m_j = 0.480 + 1.574 * acentric_j - 0.176 * pow(acentric_j, 2);
2833
2834 CoolPropDbl a_j = 0.42747 * pow(R_u * Tcj, 2) / pcj * pow(1 + m_j * (1 - sqrt(T / Tcj)), 2);
2835
2836 k_ij = 0;
2837 //if (i == j){
2838 // k_ij = 0;
2839 //}
2840 //else{
2841 // k_ij = 0;
2842 //}
2843
2844 a += mole_fractions[i] * mole_fractions[j] * sqrt(a_i * a_j) * (1 - k_ij);
2845 }
2846 }
2847
2848 CoolPropDbl A = a * p / pow(R_u * T, 2);
2849 CoolPropDbl B = b * p / (R_u * T);
2850
2851 //Solve the cubic for solutions for Z = p/(rho*R*T)
2852 double Z0, Z1, Z2;
2853 int Nsolns;
2854 solve_cubic(1, -1, A - B - B * B, -A * B, Nsolns, Z0, Z1, Z2);
2855
2856 // Determine the guess value
2857 if (Nsolns == 1) {
2858 rhomolar = p / (Z0 * R_u * T);
2859 } else {
2860 CoolPropDbl rhomolar0 = p / (Z0 * R_u * T);
2861 CoolPropDbl rhomolar1 = p / (Z1 * R_u * T);
2862 CoolPropDbl rhomolar2 = p / (Z2 * R_u * T);
2863
2864 // Check if only one solution is positive, return the solution if that is the case
2865 if (rhomolar0 > 0 && rhomolar1 <= 0 && rhomolar2 <= 0) {
2866 return rhomolar0;
2867 }
2868 if (rhomolar0 <= 0 && rhomolar1 > 0 && rhomolar2 <= 0) {
2869 return rhomolar1;
2870 }
2871 if (rhomolar0 <= 0 && rhomolar1 <= 0 && rhomolar2 > 0) {
2872 return rhomolar2;
2873 }
2874
2875 switch (phase) {
2876 case iphase_liquid:
2878 rhomolar = max3(rhomolar0, rhomolar1, rhomolar2);
2879 break;
2880 case iphase_gas:
2883 rhomolar = min3(rhomolar0, rhomolar1, rhomolar2);
2884 break;
2885 default:
2886 throw ValueError("Bad phase to solver_rho_Tp_SRK");
2887 };
2888 }
2889 return rhomolar;
2890}
2891
2893 // Calculate the reducing parameters
2895 _tau = _reducing.T / _T;
2896
2897 // Calculate derivative if needed
2898 CoolPropDbl dar_dDelta = dalphar_dDelta();
2899 CoolPropDbl R_u = gas_constant();
2900
2901 // Get pressure
2902 _p = _rhomolar * R_u * _T * (1 + _delta.pt() * dar_dDelta);
2903
2904 //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);
2905 //if (_p < 0){
2906 // throw ValueError("Pressure is less than zero");
2907 //}
2908
2909 return static_cast<CoolPropDbl>(_p);
2910}
2912 // Calculate the reducing parameters
2915
2916 // Calculate derivatives if needed, or just use cached values
2917 // Calculate derivative if needed
2921 CoolPropDbl R_u = gas_constant();
2922
2923 // Get molar enthalpy
2924 return R_u * T * (1 + tau * (da0_dTau + dar_dTau) + delta * dar_dDelta);
2925}
2927 if (get_debug_level() >= 50)
2928 std::cout << format("HelmholtzEOSMixtureBackend::calc_hmolar: 2phase: %d T: %g rhomomolar: %g", isTwoPhase(), _T, _rhomolar) << std::endl;
2929 if (isTwoPhase()) {
2930 if (!this->SatL || !this->SatV) throw ValueError(format("The saturation properties are needed for the two-phase properties"));
2931 if (std::abs(_Q) < DBL_EPSILON) {
2932 _hmolar = SatL->hmolar();
2933 } else if (std::abs(_Q - 1) < DBL_EPSILON) {
2934 _hmolar = SatV->hmolar();
2935 } else {
2936 _hmolar = _Q * SatV->hmolar() + (1 - _Q) * SatL->hmolar();
2937 }
2938 return static_cast<CoolPropDbl>(_hmolar);
2939 } else if (isHomogeneousPhase()) {
2940 // Calculate the reducing parameters
2942 _tau = _reducing.T / _T;
2943
2944 // Calculate derivatives if needed, or just use cached values
2945 CoolPropDbl da0_dTau = dalpha0_dTau();
2946 CoolPropDbl dar_dTau = dalphar_dTau();
2947 CoolPropDbl dar_dDelta = dalphar_dDelta();
2948 CoolPropDbl R_u = gas_constant();
2949
2950 // Get molar enthalpy
2951 _hmolar = R_u * _T * (1 + _tau.pt() * (da0_dTau + dar_dTau) + _delta.pt() * dar_dDelta);
2952
2953 return static_cast<CoolPropDbl>(_hmolar);
2954 } else {
2955 throw ValueError(format("phase is invalid in calc_hmolar"));
2956 }
2957}
2959 // Calculate the reducing parameters
2962
2963 // Calculate derivatives if needed, or just use cached values
2964 // Calculate derivative if needed
2969 CoolPropDbl R_u = gas_constant();
2970
2971 // Get molar entropy
2972 return R_u * (tau * (da0_dTau + dar_dTau) - a0 - ar);
2973}
2975 if (isTwoPhase()) {
2976 if (!this->SatL || !this->SatV) throw ValueError(format("The saturation properties are needed for the two-phase properties"));
2977 if (std::abs(_Q) < DBL_EPSILON) {
2978 _smolar = SatL->smolar();
2979 } else if (std::abs(_Q - 1) < DBL_EPSILON) {
2980 _smolar = SatV->smolar();
2981 } else {
2982 _smolar = _Q * SatV->smolar() + (1 - _Q) * SatL->smolar();
2983 }
2984 return static_cast<CoolPropDbl>(_smolar);
2985 } else if (isHomogeneousPhase()) {
2986 // Calculate the reducing parameters
2988 _tau = _reducing.T / _T;
2989
2990 // Calculate derivatives if needed, or just use cached values
2992 CoolPropDbl da0_dTau =
2993 ders.dalphar_dtau; // Note: while the naming here refers to alphar for historical reasons, it is actually the ideal-gas part
2994 CoolPropDbl a0 = ders.alphar; // Note: while the naming here refers to alphar for historical reasons, it is actually the ideal-gas part
2995
2996 CoolPropDbl ar = alphar();
2997 CoolPropDbl dar_dTau = dalphar_dTau();
2998 CoolPropDbl R_u = gas_constant();
2999
3000 // Get molar entropy
3001 _smolar = R_u * (_tau.pt() * (da0_dTau + dar_dTau) - a0 - ar);
3002
3003 return static_cast<CoolPropDbl>(_smolar);
3004 } else {
3005 throw ValueError(format("phase is invalid in calc_smolar"));
3006 }
3007}
3009 // Calculate the reducing parameters
3012
3013 // Calculate derivatives
3016 CoolPropDbl R_u = gas_constant();
3017
3018 // Get molar internal energy
3019 return R_u * T * tau * (da0_dTau + dar_dTau);
3020}
3022 if (isTwoPhase()) {
3023 if (!this->SatL || !this->SatV) throw ValueError(format("The saturation properties are needed for the two-phase properties"));
3024 if (std::abs(_Q) < DBL_EPSILON) {
3025 _umolar = SatL->umolar();
3026 } else if (std::abs(_Q - 1) < DBL_EPSILON) {
3027 _umolar = SatV->umolar();
3028 } else {
3029 _umolar = _Q * SatV->umolar() + (1 - _Q) * SatL->umolar();
3030 }
3031 return static_cast<CoolPropDbl>(_umolar);
3032 } else if (isHomogeneousPhase()) {
3033 // Calculate the reducing parameters
3035 _tau = _reducing.T / _T;
3036
3037 // Calculate derivatives if needed, or just use cached values
3038 CoolPropDbl da0_dTau = dalpha0_dTau();
3039 CoolPropDbl dar_dTau = dalphar_dTau();
3040 CoolPropDbl R_u = gas_constant();
3041
3042 // Get molar internal energy
3043 _umolar = R_u * _T * _tau.pt() * (da0_dTau + dar_dTau);
3044
3045 return static_cast<CoolPropDbl>(_umolar);
3046 } else {
3047 throw ValueError(format("phase is invalid in calc_umolar"));
3048 }
3049}
3051 // Calculate the reducing parameters
3053 _tau = _reducing.T / _T;
3054
3055 // Calculate derivatives if needed, or just use cached values
3056 CoolPropDbl d2ar_dTau2 = d2alphar_dTau2();
3057 CoolPropDbl d2a0_dTau2 = d2alpha0_dTau2();
3058 CoolPropDbl R_u = gas_constant();
3059
3060 // Get cv
3061 _cvmolar = -R_u * pow(_tau.pt(), 2) * (d2ar_dTau2 + d2a0_dTau2);
3062
3063 return static_cast<double>(_cvmolar);
3064}
3066 // Calculate the reducing parameters
3068 _tau = _reducing.T / _T;
3069
3070 // Calculate derivatives if needed, or just use cached values
3071 CoolPropDbl d2a0_dTau2 = d2alpha0_dTau2();
3072 CoolPropDbl dar_dDelta = dalphar_dDelta();
3073 CoolPropDbl d2ar_dDelta2 = d2alphar_dDelta2();
3074 CoolPropDbl d2ar_dDelta_dTau = d2alphar_dDelta_dTau();
3075 CoolPropDbl d2ar_dTau2 = d2alphar_dTau2();
3076 CoolPropDbl R_u = gas_constant();
3077
3078 // Get cp
3079 _cpmolar = R_u
3080 * (-pow(_tau.pt(), 2) * (d2ar_dTau2 + d2a0_dTau2)
3081 + pow(1 + _delta.pt() * dar_dDelta - _delta.pt() * _tau.pt() * d2ar_dDelta_dTau, 2)
3082 / (1 + 2 * _delta.pt() * dar_dDelta + pow(_delta.pt(), 2) * d2ar_dDelta2));
3083
3084 return static_cast<double>(_cpmolar);
3085}
3087 // Calculate the reducing parameters
3089 _tau = _reducing.T / _T;
3090
3091 // Calculate derivatives if needed, or just use cached values
3092 CoolPropDbl d2a0_dTau2 = d2alpha0_dTau2();
3093 CoolPropDbl R_u = gas_constant();
3094
3095 // Get cp of the ideal gas
3096 return R_u * (1 + (-pow(_tau.pt(), 2)) * d2a0_dTau2);
3097}
3099 if (isTwoPhase()) {
3100 if (std::abs(_Q) < DBL_EPSILON) {
3101 return SatL->speed_sound();
3102 } else if (std::abs(_Q - 1) < DBL_EPSILON) {
3103 return SatV->speed_sound();
3104 } else {
3105 throw ValueError(format("Speed of sound is not defined for two-phase states because it depends on the distribution of phases."));
3106 }
3107 } else if (isHomogeneousPhase()) {
3108 // Calculate the reducing parameters
3110 _tau = _reducing.T / _T;
3111
3112 // Calculate derivatives if needed, or just use cached values
3113 CoolPropDbl d2a0_dTau2 = d2alpha0_dTau2();
3114 CoolPropDbl dar_dDelta = dalphar_dDelta();
3115 CoolPropDbl d2ar_dDelta2 = d2alphar_dDelta2();
3116 CoolPropDbl d2ar_dDelta_dTau = d2alphar_dDelta_dTau();
3117 CoolPropDbl d2ar_dTau2 = d2alphar_dTau2();
3118 CoolPropDbl R_u = gas_constant();
3119 CoolPropDbl mm = molar_mass();
3120
3121 // Get speed of sound
3122 _speed_sound = sqrt(
3123 R_u * _T / mm
3124 * (1 + 2 * _delta.pt() * dar_dDelta + pow(_delta.pt(), 2) * d2ar_dDelta2
3125 - pow(1 + _delta.pt() * dar_dDelta - _delta.pt() * _tau.pt() * d2ar_dDelta_dTau, 2) / (pow(_tau.pt(), 2) * (d2ar_dTau2 + d2a0_dTau2))));
3126
3127 return static_cast<CoolPropDbl>(_speed_sound);
3128 } else {
3129 throw ValueError(format("phase is invalid in calc_speed_sound"));
3130 }
3131}
3132
3134 // Calculate the reducing parameters
3137
3138 // Calculate derivatives
3142
3143 // Get molar gibbs function
3144 return gas_constant() * T * (1 + a0 + ar + delta * dar_dDelta);
3145}
3147 if (isTwoPhase()) {
3148 if (!this->SatL || !this->SatV) throw ValueError(format("The saturation properties are needed for the two-phase properties"));
3149 _gibbsmolar = _Q * SatV->gibbsmolar() + (1 - _Q) * SatL->gibbsmolar();
3150 return static_cast<CoolPropDbl>(_gibbsmolar);
3151 } else if (isHomogeneousPhase()) {
3152 // Calculate the reducing parameters
3154 _tau = _reducing.T / _T;
3155
3156 // Calculate derivatives if needed, or just use cached values
3157 CoolPropDbl ar = alphar();
3158 CoolPropDbl a0 = alpha0();
3159 CoolPropDbl dar_dDelta = dalphar_dDelta();
3160 CoolPropDbl R_u = gas_constant();
3161
3162 // Get molar gibbs function
3163 _gibbsmolar = R_u * _T * (1 + a0 + ar + _delta.pt() * dar_dDelta);
3164
3165 return static_cast<CoolPropDbl>(_gibbsmolar);
3166 } else {
3167 throw ValueError(format("phase is invalid in calc_gibbsmolar"));
3168 }
3169}
3172 _umolar_excess = this->umolar();
3173 _volumemolar_excess = 1 / this->rhomolar();
3174 for (std::size_t i = 0; i < components.size(); ++i) {
3176 transient_pure_state->update(PT_INPUTS, p(), T());
3177 double x_i = mole_fractions[i];
3178 double R = gas_constant();
3179 _gibbsmolar_excess = static_cast<double>(_gibbsmolar_excess) - x_i * (transient_pure_state->gibbsmolar() + R * T() * log(x_i));
3180 _hmolar_excess = static_cast<double>(_hmolar_excess) - x_i * transient_pure_state->hmolar();
3181 _umolar_excess = static_cast<double>(_umolar_excess) - x_i * transient_pure_state->umolar();
3182 _smolar_excess = static_cast<double>(_smolar_excess) - x_i * (transient_pure_state->smolar() - R * log(x_i));
3183 _volumemolar_excess = static_cast<double>(_volumemolar_excess) - x_i / transient_pure_state->rhomolar();
3184 }
3185 _helmholtzmolar_excess = static_cast<double>(_umolar_excess) - _T * static_cast<double>(_smolar_excess);
3186}
3187
3189 if (isTwoPhase()) {
3190 if (!this->SatL || !this->SatV) throw ValueError(format("The saturation properties are needed for the two-phase properties"));
3191 _helmholtzmolar = _Q * SatV->helmholtzmolar() + (1 - _Q) * SatL->helmholtzmolar();
3192 return static_cast<CoolPropDbl>(_helmholtzmolar);
3193 } else if (isHomogeneousPhase()) {
3194 // Calculate the reducing parameters
3196 _tau = _reducing.T / _T;
3197
3198 // Calculate derivatives if needed, or just use cached values
3199 CoolPropDbl ar = alphar();
3200 CoolPropDbl a0 = alpha0();
3201 CoolPropDbl R_u = gas_constant();
3202
3203 // Get molar Helmholtz energy
3204 _helmholtzmolar = R_u * _T * (a0 + ar);
3205
3206 return static_cast<CoolPropDbl>(_helmholtzmolar);
3207 } else {
3208 throw ValueError(format("phase is invalid in calc_helmholtzmolar"));
3209 }
3210}
3213 return exp(MixtureDerivatives::ln_fugacity_coefficient(*this, i, xN_flag));
3214}
3217 return MixtureDerivatives::fugacity_i(*this, i, xN_flag);
3218}
3221 double Tci = get_fluid_constant(i, iT_critical);
3222 double rhoci = get_fluid_constant(i, irhomolar_critical);
3223 double dnar_dni__const_T_V_nj = MixtureDerivatives::dnalphar_dni__constT_V_nj(*this, i, xN_flag);
3224 double dna0_dni__const_T_V_nj =
3225 components[i].EOS().alpha0.base(tau() * (Tci / T_reducing()), delta() / (rhoci / rhomolar_reducing())) + 1 + log(mole_fractions[i]);
3226 return gas_constant() * T() * (dna0_dni__const_T_V_nj + dnar_dni__const_T_V_nj);
3227}
3229 return 2
3230 - rhomolar()
3233}
3234
3235SimpleState HelmholtzEOSMixtureBackend::calc_reducing_state_nocache(const std::vector<CoolPropDbl>& mole_fractions) {
3236 SimpleState reducing;
3238 reducing = components[0].EOS().reduce;
3239 } else {
3240 reducing.T = Reducing->Tr(mole_fractions);
3241 reducing.rhomolar = Reducing->rhormolar(mole_fractions);
3242 }
3243 return reducing;
3244}
3246 if (get_mole_fractions_ref().empty()) {
3247 throw ValueError("Mole fractions must be set before calling calc_reducing_state");
3248 }
3251 _crit = _reducing;
3252}
3253void HelmholtzEOSMixtureBackend::calc_all_alphar_deriv_cache(const std::vector<CoolPropDbl>& mole_fractions, const CoolPropDbl& tau,
3254 const CoolPropDbl& delta) {
3255 deriv_counter++;
3256 bool cache_values = true;
3257 HelmholtzDerivatives derivs = residual_helmholtz->all(*this, get_mole_fractions_ref(), tau, delta, cache_values);
3258 _alphar = derivs.alphar;
3259 _dalphar_dDelta = derivs.dalphar_ddelta;
3260 _dalphar_dTau = derivs.dalphar_dtau;
3261 _d2alphar_dDelta2 = derivs.d2alphar_ddelta2;
3262 _d2alphar_dDelta_dTau = derivs.d2alphar_ddelta_dtau;
3263 _d2alphar_dTau2 = derivs.d2alphar_dtau2;
3264 _d3alphar_dDelta3 = derivs.d3alphar_ddelta3;
3265 _d3alphar_dDelta2_dTau = derivs.d3alphar_ddelta2_dtau;
3266 _d3alphar_dDelta_dTau2 = derivs.d3alphar_ddelta_dtau2;
3267 _d3alphar_dTau3 = derivs.d3alphar_dtau3;
3268 _d4alphar_dDelta4 = derivs.d4alphar_ddelta4;
3269 _d4alphar_dDelta3_dTau = derivs.d4alphar_ddelta3_dtau;
3270 _d4alphar_dDelta2_dTau2 = derivs.d4alphar_ddelta2_dtau2;
3271 _d4alphar_dDelta_dTau3 = derivs.d4alphar_ddelta_dtau3;
3272 _d4alphar_dTau4 = derivs.d4alphar_dtau4;
3273}
3274
3275CoolPropDbl HelmholtzEOSMixtureBackend::calc_alphar_deriv_nocache(const int nTau, const int nDelta, const std::vector<CoolPropDbl>& mole_fractions,
3276 const CoolPropDbl& tau, const CoolPropDbl& delta) {
3277 bool cache_values = false;
3278 HelmholtzDerivatives derivs = residual_helmholtz->all(*this, mole_fractions, tau, delta, cache_values);
3279 return derivs.get(nTau, nDelta);
3280}
3281CoolPropDbl HelmholtzEOSMixtureBackend::calc_alpha0_deriv_nocache(const int nTau, const int nDelta, const std::vector<CoolPropDbl>& mole_fractions,
3282 const CoolPropDbl& tau, const CoolPropDbl& delta, const CoolPropDbl& Tr,
3283 const CoolPropDbl& rhor) {
3284 CoolPropDbl val;
3285 if (components.size() == 0) {
3286 throw ValueError("No alpha0 derivatives are available");
3287 }
3289 EquationOfState& E = components[0].EOS();
3290 // In the case of cubics, we need to use the shifted tau^*=Tc/T and delta^*=rho/rhoc
3291 // rather than tau=Tr/T and delta=rho/rhor
3292 // For multiparameter EOS, this changes nothing because Tc/Tr = 1 and rhoc/rhor = 1
3294
3295 // Cache the reducing temperature in some terms that need it (GERG-2004 models)
3296 E.alpha0.set_Tred(Tc);
3297 double taustar = Tc / Tr * tau, deltastar = rhor / rhomolarc * delta;
3298 if (nTau == 0 && nDelta == 0) {
3299 val = E.base0(taustar, deltastar);
3300 } else if (nTau == 0 && nDelta == 1) {
3301 val = E.dalpha0_dDelta(taustar, deltastar);
3302 } else if (nTau == 1 && nDelta == 0) {
3303 val = E.dalpha0_dTau(taustar, deltastar);
3304 } else if (nTau == 0 && nDelta == 2) {
3305 val = E.d2alpha0_dDelta2(taustar, deltastar);
3306 } else if (nTau == 1 && nDelta == 1) {
3307 val = E.d2alpha0_dDelta_dTau(taustar, deltastar);
3308 } else if (nTau == 2 && nDelta == 0) {
3309 val = E.d2alpha0_dTau2(taustar, deltastar);
3310 } else if (nTau == 0 && nDelta == 3) {
3311 val = E.d3alpha0_dDelta3(taustar, deltastar);
3312 } else if (nTau == 1 && nDelta == 2) {
3313 val = E.d3alpha0_dDelta2_dTau(taustar, deltastar);
3314 } else if (nTau == 2 && nDelta == 1) {
3315 val = E.d3alpha0_dDelta_dTau2(taustar, deltastar);
3316 } else if (nTau == 3 && nDelta == 0) {
3317 val = E.d3alpha0_dTau3(taustar, deltastar);
3318 } else {
3319 throw ValueError();
3320 }
3321 val *= pow(rhor / rhomolarc, nDelta);
3322 val /= pow(Tr / Tc, nTau);
3323 if (!ValidNumber(val)) {
3324 //calc_alpha0_deriv_nocache(nTau,nDelta,mole_fractions,tau,delta,Tr,rhor);
3325 throw ValueError(format("calc_alpha0_deriv_nocache returned invalid number with inputs nTau: %d, nDelta: %d, tau: %Lg, delta: %Lg", nTau,
3326 nDelta, tau, delta));
3327 } else {
3328 return val;
3329 }
3330 } else {
3331 // See Table B5, GERG 2008 from Kunz Wagner, JCED, 2012
3332 std::size_t N = mole_fractions.size();
3333 CoolPropDbl summer = 0;
3334 CoolPropDbl tau_i, delta_i, rho_ci, T_ci;
3335 CoolPropDbl Rmix = gas_constant();
3336 for (unsigned int i = 0; i < N; ++i) {
3337
3341 tau_i = T_ci * tau / Tr;
3342 delta_i = delta * rhor / rho_ci;
3343 CoolPropDbl Rratio = Rcomponent / Rmix;
3344
3345 // Cache the reducing temperature in some terms that need it (GERG-2004 models)
3346 components[i].EOS().alpha0.set_Tred(Tr);
3347
3348 if (nTau == 0 && nDelta == 0) {
3349 double logxi = (std::abs(mole_fractions[i]) > DBL_EPSILON) ? log(mole_fractions[i]) : 0;
3350 summer += mole_fractions[i] * Rratio * (components[i].EOS().base0(tau_i, delta_i) + logxi);
3351 } else if (nTau == 0 && nDelta == 1) {
3352 summer += mole_fractions[i] * Rratio * rhor / rho_ci * components[i].EOS().dalpha0_dDelta(tau_i, delta_i);
3353 } else if (nTau == 1 && nDelta == 0) {
3354 summer += mole_fractions[i] * Rratio * T_ci / Tr * components[i].EOS().dalpha0_dTau(tau_i, delta_i);
3355 } else if (nTau == 0 && nDelta == 2) {
3356 summer += mole_fractions[i] * Rratio * pow(rhor / rho_ci, 2) * components[i].EOS().d2alpha0_dDelta2(tau_i, delta_i);
3357 } else if (nTau == 1 && nDelta == 1) {
3358 summer += mole_fractions[i] * Rratio * rhor / rho_ci * T_ci / Tr * components[i].EOS().d2alpha0_dDelta_dTau(tau_i, delta_i);
3359 } else if (nTau == 2 && nDelta == 0) {
3360 summer += mole_fractions[i] * Rratio * pow(T_ci / Tr, 2) * components[i].EOS().d2alpha0_dTau2(tau_i, delta_i);
3361 } else {
3362 throw ValueError();
3363 }
3364 }
3365 return summer;
3366 }
3367}
3368
3370 const CoolPropDbl& tau, const CoolPropDbl& delta,
3371 const CoolPropDbl& Tr, const CoolPropDbl& rhor) {
3372 if (components.size() == 0) {
3373 throw ValueError("No alpha0 derivatives are available");
3374 }
3376 EquationOfState& E = components[0].EOS();
3377 // In the case of cubics, we need to use the shifted tau^*=Tc/T and delta^*=rho/rhoc
3378 // rather than tau=Tr/T and delta=rho/rhor
3379 // For multiparameter EOS, this changes nothing because Tc/Tr = 1 and rhoc/rhor = 1
3381
3382 // Cache the reducing temperature in some terms that need it (GERG-2004 models)
3383 E.alpha0.set_Tred(Tc);
3384 double taustar = Tc / Tr * tau, deltastar = rhor / rhomolarc * delta;
3385 return E.alpha0.all(taustar, deltastar, false);
3386 } else {
3388
3389 // See Table B5, GERG 2008 from Kunz Wagner, JCED, 2012
3390 std::size_t N = mole_fractions.size();
3391 CoolPropDbl summer_00 = 0, summer_01 = 0, summer_10 = 0, summer_02 = 0, summer_11 = 0, summer_20 = 0;
3392 CoolPropDbl tau_i, delta_i, rho_ci, T_ci;
3393 CoolPropDbl Rmix = gas_constant();
3394 for (unsigned int i = 0; i < N; ++i) {
3395
3399 tau_i = T_ci * tau / Tr;
3400 delta_i = delta * rhor / rho_ci;
3401 CoolPropDbl Rratio = Rcomponent / Rmix;
3402
3403 // Cache the reducing temperature in some terms that need it (GERG-2004 models)
3404 components[i].EOS().alpha0.set_Tred(Tr);
3405 {
3406 // All the ideal-gas derivatives for the pure component
3407 auto pure = components[i].EOS().alpha0.all(tau_i, delta_i);
3408
3409 double logxi = (std::abs(mole_fractions[i]) > DBL_EPSILON) ? log(mole_fractions[i]) : 0;
3410 // Note: you see alphar here, that is a book-keeping artifact, it is really alpha for ideal gas
3411 summer_00 += mole_fractions[i] * Rratio * (pure.alphar + logxi);
3412 summer_01 += mole_fractions[i] * Rratio * rhor / rho_ci * pure.dalphar_ddelta;
3413 summer_10 += mole_fractions[i] * Rratio * T_ci / Tr * pure.dalphar_dtau;
3414 summer_02 += mole_fractions[i] * Rratio * pow(rhor / rho_ci, 2) * pure.d2alphar_ddelta2;
3415 summer_11 += mole_fractions[i] * Rratio * rhor / rho_ci * T_ci / Tr * pure.d2alphar_ddelta_dtau;
3416 summer_20 += mole_fractions[i] * Rratio * pow(T_ci / Tr, 2) * pure.d2alphar_dtau2;
3417 }
3418 }
3419 // Note: you see alphar here, that is a book-keeping artifact, it is really alpha for ideal gas
3420 ders.alphar = summer_00;
3421 ders.dalphar_ddelta = summer_01;
3422 ders.dalphar_dtau = summer_10;
3423 ders.d2alphar_ddelta2 = summer_02;
3424 ders.d2alphar_ddelta_dtau = summer_11;
3425 ders.d2alphar_dtau2 = summer_20;
3426
3427 return ders;
3428 }
3429}
3430
3433 return static_cast<CoolPropDbl>(_alphar);
3434}
3437 return static_cast<CoolPropDbl>(_dalphar_dDelta);
3438}
3441 return static_cast<CoolPropDbl>(_dalphar_dTau);
3442}
3445 return static_cast<CoolPropDbl>(_d2alphar_dTau2);
3446}
3449 return static_cast<CoolPropDbl>(_d2alphar_dDelta_dTau);
3450}
3453 return static_cast<CoolPropDbl>(_d2alphar_dDelta2);
3454}
3457 return static_cast<CoolPropDbl>(_d3alphar_dDelta3);
3458}
3461 return static_cast<CoolPropDbl>(_d3alphar_dDelta2_dTau);
3462}
3465 return static_cast<CoolPropDbl>(_d3alphar_dDelta_dTau2);
3466}
3469 return static_cast<CoolPropDbl>(_d3alphar_dTau3);
3470}
3471
3474 return static_cast<CoolPropDbl>(_d4alphar_dDelta4);
3475}
3478 return static_cast<CoolPropDbl>(_d4alphar_dDelta3_dTau);
3479}
3482 return static_cast<CoolPropDbl>(_d4alphar_dDelta2_dTau2);
3483}
3486 return static_cast<CoolPropDbl>(_d4alphar_dDelta_dTau3);
3487}
3490 return static_cast<CoolPropDbl>(_d4alphar_dTau4);
3491}
3492
3494 const int nTau = 0, nDelta = 0;
3496}
3498 const int nTau = 0, nDelta = 1;
3500}
3502 const int nTau = 1, nDelta = 0;
3504}
3506 const int nTau = 0, nDelta = 2;
3508}
3510 const int nTau = 1, nDelta = 1;
3512}
3514 const int nTau = 2, nDelta = 0;
3516}
3518 const int nTau = 0, nDelta = 3;
3520}
3522 const int nTau = 1, nDelta = 2;
3524}
3526 const int nTau = 2, nDelta = 1;
3528}
3530 const int nTau = 3, nDelta = 0;
3532}
3535 // Derivative of temperature w.r.t. pressure ALONG the saturation curve
3536 CoolPropDbl dTdP_sat = T() * (1 / SatV.rhomolar() - 1 / SatL.rhomolar()) / (SatV.hmolar() - SatL.hmolar());
3537
3538 // "Trivial" inputs
3539 if (Of1 == iT && Wrt1 == iP) {
3540 return dTdP_sat;
3541 } else if (Of1 == iP && Wrt1 == iT) {
3542 return 1 / dTdP_sat;
3543 }
3544 // Derivative taken with respect to T
3545 else if (Wrt1 == iT) {
3546 return first_partial_deriv(Of1, iT, iP) + first_partial_deriv(Of1, iP, iT) / dTdP_sat;
3547 }
3548 // Derivative taken with respect to p
3549 else if (Wrt1 == iP) {
3550 return first_partial_deriv(Of1, iP, iT) + first_partial_deriv(Of1, iT, iP) * dTdP_sat;
3551 } else {
3552 throw ValueError(
3553 format("Not possible to take first saturation derivative with respect to %s", get_parameter_information(Wrt1, "short").c_str()));
3554 }
3555}
3557 if (!this->SatL || !this->SatV) throw ValueError(format("The saturation properties are needed for calc_first_saturation_deriv"));
3558
3559 // Derivative of temperature w.r.t. pressure ALONG the saturation curve
3560 CoolPropDbl dTdP_sat = T() * (1 / SatV->rhomolar() - 1 / SatL->rhomolar()) / (SatV->hmolar() - SatL->hmolar());
3561
3562 // "Trivial" inputs
3563 if (Of1 == iT && Wrt1 == iP) {
3564 return dTdP_sat;
3565 } else if (Of1 == iP && Wrt1 == iT) {
3566 return 1 / dTdP_sat;
3567 }
3568 // Derivative taken with respect to T
3569 else if (Wrt1 == iT) {
3570 return first_partial_deriv(Of1, iT, iP) + first_partial_deriv(Of1, iP, iT) / dTdP_sat;
3571 }
3572 // Derivative taken with respect to p
3573 else if (Wrt1 == iP) {
3574 return first_partial_deriv(Of1, iP, iT) + first_partial_deriv(Of1, iT, iP) * dTdP_sat;
3575 } else {
3576 throw ValueError(
3577 format("Not possible to take first saturation derivative with respect to %s", get_parameter_information(Wrt1, "short").c_str()));
3578 }
3579}
3581 if (!this->SatL || !this->SatV) throw ValueError(format("The saturation properties are needed for calc_second_saturation_deriv"));
3582 if (Wrt1 == iP && Wrt2 == iP) {
3583 CoolPropDbl dydT_constp = this->first_partial_deriv(Of1, iT, iP);
3584 CoolPropDbl d2ydTdp = this->second_partial_deriv(Of1, iT, iP, iP, iT);
3585 CoolPropDbl d2ydp2_constT = this->second_partial_deriv(Of1, iP, iT, iP, iT);
3586 CoolPropDbl d2ydT2_constp = this->second_partial_deriv(Of1, iT, iP, iT, iP);
3587
3588 CoolPropDbl dTdp_along_sat = calc_first_saturation_deriv(iT, iP);
3589 CoolPropDbl dvdrhoL = -1 / POW2(SatL->rhomolar());
3590 CoolPropDbl dvdrhoV = -1 / POW2(SatV->rhomolar());
3591 CoolPropDbl DELTAv = 1 / SatV->rhomolar() - 1 / SatL->rhomolar();
3592 CoolPropDbl dDELTAv_dT_constp = dvdrhoV * SatV->first_partial_deriv(iDmolar, iT, iP) - dvdrhoL * SatL->first_partial_deriv(iDmolar, iT, iP);
3593 CoolPropDbl dDELTAv_dp_constT = dvdrhoV * SatV->first_partial_deriv(iDmolar, iP, iT) - dvdrhoL * SatL->first_partial_deriv(iDmolar, iP, iT);
3594 CoolPropDbl DELTAh = SatV->hmolar() - SatL->hmolar();
3595 CoolPropDbl dDELTAh_dT_constp = SatV->first_partial_deriv(iHmolar, iT, iP) - SatL->first_partial_deriv(iHmolar, iT, iP);
3596 CoolPropDbl dDELTAh_dp_constT = SatV->first_partial_deriv(iHmolar, iP, iT) - SatL->first_partial_deriv(iHmolar, iP, iT);
3597 CoolPropDbl ddT_dTdp_along_sat_constp = (DELTAh * (_T * dDELTAv_dT_constp + DELTAv) - _T * DELTAv * dDELTAh_dT_constp) / POW2(DELTAh);
3598 CoolPropDbl ddp_dTdp_along_sat_constT = (DELTAh * (_T * dDELTAv_dp_constT) - _T * DELTAv * dDELTAh_dp_constT) / POW2(DELTAh);
3599
3600 double ddp_dydpsigma = d2ydp2_constT + dydT_constp * ddp_dTdp_along_sat_constT + d2ydTdp * dTdp_along_sat;
3601 double ddT_dydpsigma = d2ydTdp + dydT_constp * ddT_dTdp_along_sat_constp + d2ydT2_constp * dTdp_along_sat;
3602 return ddp_dydpsigma + ddT_dydpsigma * dTdp_along_sat;
3603 } else {
3604 throw ValueError(format("Currently, only possible to take second saturation derivative w.r.t. P (both times)"));
3605 }
3606}
3607
3609 parameters Constant2) {
3610 if (!this->SatL || !this->SatV) throw ValueError(format("The saturation properties are needed for calc_second_two_phase_deriv"));
3611
3612 if (Of == iDmolar
3613 && ((Wrt1 == iHmolar && Constant1 == iP && Wrt2 == iP && Constant2 == iHmolar)
3614 || (Wrt2 == iHmolar && Constant2 == iP && Wrt1 == iP && Constant1 == iHmolar))) {
3615 parameters h_key = iHmolar, rho_key = iDmolar, p_key = iP;
3616 // taking the derivative of (drho/dv)*(dv/dh|p) with respect to p with h constant
3617 CoolPropDbl dv_dh_constp = calc_first_two_phase_deriv(rho_key, h_key, p_key) / (-POW2(rhomolar()));
3618 CoolPropDbl drhomolar_dp__consth = first_two_phase_deriv(rho_key, p_key, h_key);
3619
3620 // Calculate the derivative of dvdh|p with respect to p at constant h
3621 CoolPropDbl dhL_dp_sat = SatL->calc_first_saturation_deriv(h_key, p_key, *SatL, *SatV);
3622 CoolPropDbl dhV_dp_sat = SatV->calc_first_saturation_deriv(h_key, p_key, *SatL, *SatV);
3623 CoolPropDbl drhoL_dp_sat = SatL->calc_first_saturation_deriv(rho_key, p_key, *SatL, *SatV);
3624 CoolPropDbl drhoV_dp_sat = SatV->calc_first_saturation_deriv(rho_key, p_key, *SatL, *SatV);
3625 CoolPropDbl numerator = 1 / SatV->keyed_output(rho_key) - 1 / SatL->keyed_output(rho_key);
3626 CoolPropDbl denominator = SatV->keyed_output(h_key) - SatL->keyed_output(h_key);
3627 CoolPropDbl dnumerator = -1 / POW2(SatV->keyed_output(rho_key)) * drhoV_dp_sat + 1 / POW2(SatL->keyed_output(rho_key)) * drhoL_dp_sat;
3628 CoolPropDbl ddenominator = dhV_dp_sat - dhL_dp_sat;
3629 CoolPropDbl d_dvdh_dp__consth = (denominator * dnumerator - numerator * ddenominator) / POW2(denominator);
3630 return -POW2(rhomolar()) * d_dvdh_dp__consth + dv_dh_constp * (-2 * rhomolar()) * drhomolar_dp__consth;
3631 } else if (Of == iDmass
3632 && ((Wrt1 == iHmass && Constant1 == iP && Wrt2 == iP && Constant2 == iHmass)
3633 || (Wrt2 == iHmass && Constant2 == iP && Wrt1 == iP && Constant1 == iHmass))) {
3634 parameters h_key = iHmass, rho_key = iDmass, p_key = iP;
3635 CoolPropDbl rho = keyed_output(rho_key);
3636 // taking the derivative of (drho/dv)*(dv/dh|p) with respect to p with h constant
3637 CoolPropDbl dv_dh_constp = calc_first_two_phase_deriv(rho_key, h_key, p_key) / (-POW2(rho));
3638 CoolPropDbl drho_dp__consth = first_two_phase_deriv(rho_key, p_key, h_key);
3639
3640 // Calculate the derivative of dvdh|p with respect to p at constant h
3641 CoolPropDbl dhL_dp_sat = SatL->calc_first_saturation_deriv(h_key, p_key, *SatL, *SatV);
3642 CoolPropDbl dhV_dp_sat = SatV->calc_first_saturation_deriv(h_key, p_key, *SatL, *SatV);
3643 CoolPropDbl drhoL_dp_sat = SatL->calc_first_saturation_deriv(rho_key, p_key, *SatL, *SatV);
3644 CoolPropDbl drhoV_dp_sat = SatV->calc_first_saturation_deriv(rho_key, p_key, *SatL, *SatV);
3645 CoolPropDbl numerator = 1 / SatV->keyed_output(rho_key) - 1 / SatL->keyed_output(rho_key);
3646 CoolPropDbl denominator = SatV->keyed_output(h_key) - SatL->keyed_output(h_key);
3647 CoolPropDbl dnumerator = -1 / POW2(SatV->keyed_output(rho_key)) * drhoV_dp_sat + 1 / POW2(SatL->keyed_output(rho_key)) * drhoL_dp_sat;
3648 CoolPropDbl ddenominator = dhV_dp_sat - dhL_dp_sat;
3649 CoolPropDbl d_dvdh_dp__consth = (denominator * dnumerator - numerator * ddenominator) / POW2(denominator);
3650 return -POW2(rho) * d_dvdh_dp__consth + dv_dh_constp * (-2 * rho) * drho_dp__consth;
3651 } else {
3652 throw ValueError();
3653 }
3654}
3656 if (!this->SatL || !this->SatV) throw ValueError(format("The saturation properties are needed for calc_first_two_phase_deriv"));
3657 if (Of == iDmolar && Wrt == iHmolar && Constant == iP) {
3658 return -POW2(rhomolar()) * (1 / SatV->rhomolar() - 1 / SatL->rhomolar()) / (SatV->hmolar() - SatL->hmolar());
3659 } else if (Of == iDmass && Wrt == iHmass && Constant == iP) {
3660 return -POW2(rhomass()) * (1 / SatV->rhomass() - 1 / SatL->rhomass()) / (SatV->hmass() - SatL->hmass());
3661 } else if (Of == iDmolar && Wrt == iP && Constant == iHmolar) {
3662 // v = 1/rho; drhodv = -rho^2; dvdrho = -1/rho^2
3663 CoolPropDbl dvdrhoL = -1 / POW2(SatL->rhomolar());
3664 CoolPropDbl dvdrhoV = -1 / POW2(SatV->rhomolar());
3665 CoolPropDbl dvL_dp = dvdrhoL * SatL->calc_first_saturation_deriv(iDmolar, iP, *SatL, *SatV);
3666 CoolPropDbl dvV_dp = dvdrhoV * SatV->calc_first_saturation_deriv(iDmolar, iP, *SatL, *SatV);
3667 CoolPropDbl dhL_dp = SatL->calc_first_saturation_deriv(iHmolar, iP, *SatL, *SatV);
3668 CoolPropDbl dhV_dp = SatV->calc_first_saturation_deriv(iHmolar, iP, *SatL, *SatV);
3669 CoolPropDbl dxdp_h = (Q() * dhV_dp + (1 - Q()) * dhL_dp) / (SatL->hmolar() - SatV->hmolar());
3670 CoolPropDbl dvdp_h = dvL_dp + dxdp_h * (1 / SatV->rhomolar() - 1 / SatL->rhomolar()) + Q() * (dvV_dp - dvL_dp);
3671 return -POW2(rhomolar()) * dvdp_h;
3672 } else if (Of == iDmass && Wrt == iP && Constant == iHmass) {
3673 // v = 1/rho; drhodv = -rho^2; dvdrho = -1/rho^2
3674 CoolPropDbl dvdrhoL = -1 / POW2(SatL->rhomass());
3675 CoolPropDbl dvdrhoV = -1 / POW2(SatV->rhomass());
3676 CoolPropDbl dvL_dp = dvdrhoL * SatL->calc_first_saturation_deriv(iDmass, iP, *SatL, *SatV);
3677 CoolPropDbl dvV_dp = dvdrhoV * SatV->calc_first_saturation_deriv(iDmass, iP, *SatL, *SatV);
3678 CoolPropDbl dhL_dp = SatL->calc_first_saturation_deriv(iHmass, iP, *SatL, *SatV);
3679 CoolPropDbl dhV_dp = SatV->calc_first_saturation_deriv(iHmass, iP, *SatL, *SatV);
3680 CoolPropDbl dxdp_h = (Q() * dhV_dp + (1 - Q()) * dhL_dp) / (SatL->hmass() - SatV->hmass());
3681 CoolPropDbl dvdp_h = dvL_dp + dxdp_h * (1 / SatV->rhomass() - 1 / SatL->rhomass()) + Q() * (dvV_dp - dvL_dp);
3682 return -POW2(rhomass()) * dvdp_h;
3683 } else {
3684 throw ValueError("These inputs are not supported to calc_first_two_phase_deriv");
3685 }
3686}
3688 // Note: If you need all three values (drho_dh__p, drho_dp__h and rho_spline),
3689 // you should calculate drho_dp__h first to avoid duplicate calculations.
3690
3691 bool drho_dh__p = false;
3692 bool drho_dp__h = false;
3693 bool rho_spline = false;
3694
3695 if (Of == iDmolar && Wrt == iHmolar && Constant == iP) {
3696 drho_dh__p = true;
3698 } else if (Of == iDmass && Wrt == iHmass && Constant == iP) {
3700 } else if (Of == iDmolar && Wrt == iP && Constant == iHmolar) {
3701 drho_dp__h = true;
3703 } else if (Of == iDmass && Wrt == iP && Constant == iHmass) {
3705 }
3706 // Add the special case for the splined density
3707 else if (Of == iDmolar && Wrt == iDmolar && Constant == iDmolar) {
3708 rho_spline = true;
3709 if (_rho_spline) return _rho_spline;
3710 } else if (Of == iDmass && Wrt == iDmass && Constant == iDmass) {
3712 } else {
3713 throw ValueError("These inputs are not supported to calc_first_two_phase_deriv");
3714 }
3715
3716 if (!this->SatL || !this->SatV) throw ValueError(format("The saturation properties are needed for calc_first_two_phase_deriv_splined"));
3717 if (_Q > x_end) {
3718 throw ValueError(format("Q [%g] is greater than x_end [%Lg]", _Q, x_end).c_str());
3719 }
3720 if (_phase != iphase_twophase) {
3721 throw ValueError(format("state is not two-phase"));
3722 }
3723
3724 shared_ptr<HelmholtzEOSMixtureBackend> Liq(new HelmholtzEOSMixtureBackend(this->get_components())),
3726
3727 Liq->specify_phase(iphase_liquid);
3728 Liq->_Q = -1;
3729 Liq->update_DmolarT_direct(SatL->rhomolar(), SatL->T());
3730 End->update(QT_INPUTS, x_end, SatL->T());
3731
3732 CoolPropDbl Delta = Q() * (SatV->keyed_output(iHmolar) - SatL->keyed_output(iHmolar));
3733 CoolPropDbl Delta_end = End->keyed_output(iHmolar) - SatL->keyed_output(iHmolar);
3734
3735 // At the end of the zone to which spline is applied
3736 CoolPropDbl drho_dh_end = End->calc_first_two_phase_deriv(iDmolar, iHmolar, iP);
3737 CoolPropDbl rho_end = End->keyed_output(iDmolar);
3738
3739 // Faking single-phase
3740 CoolPropDbl rho_liq = Liq->keyed_output(iDmolar);
3741 CoolPropDbl drho_dh_liq__constp = Liq->first_partial_deriv(iDmolar, iHmolar, iP);
3742
3743 // Spline coordinates a, b, c, d
3744 CoolPropDbl Abracket = (2 * rho_liq - 2 * rho_end + Delta_end * (drho_dh_liq__constp + drho_dh_end));
3745 CoolPropDbl a = 1 / POW3(Delta_end) * Abracket;
3746 CoolPropDbl b = 3 / POW2(Delta_end) * (-rho_liq + rho_end) - 1 / Delta_end * (drho_dh_end + 2 * drho_dh_liq__constp);
3747 CoolPropDbl c = drho_dh_liq__constp;
3748 CoolPropDbl d = rho_liq;
3749
3750 // Either the spline value or drho/dh|p can be directly evaluated now
3751 _rho_spline = a * POW3(Delta) + b * POW2(Delta) + c * Delta + d;
3752 _drho_spline_dh__constp = 3 * a * POW2(Delta) + 2 * b * Delta + c;
3753 if (rho_spline) return _rho_spline;
3754 if (drho_dh__p) return _drho_spline_dh__constp;
3755
3756 // It's drho/dp|h
3757 // ... calculate some more things
3758
3759 // Derivatives *along* the saturation curve using the special internal method
3760 CoolPropDbl dhL_dp_sat = SatL->calc_first_saturation_deriv(iHmolar, iP, *SatL, *SatV);
3761 CoolPropDbl dhV_dp_sat = SatV->calc_first_saturation_deriv(iHmolar, iP, *SatL, *SatV);
3762 CoolPropDbl drhoL_dp_sat = SatL->calc_first_saturation_deriv(iDmolar, iP, *SatL, *SatV);
3763 CoolPropDbl drhoV_dp_sat = SatV->calc_first_saturation_deriv(iDmolar, iP, *SatL, *SatV);
3764 CoolPropDbl rhoV = SatV->keyed_output(iDmolar);
3765 CoolPropDbl rhoL = SatL->keyed_output(iDmolar);
3766 CoolPropDbl drho_dp_end = POW2(End->keyed_output(iDmolar)) * (x_end / POW2(rhoV) * drhoV_dp_sat + (1 - x_end) / POW2(rhoL) * drhoL_dp_sat);
3767
3768 // Faking single-phase
3769 //CoolPropDbl drho_dp__consth_liq = Liq->first_partial_deriv(iDmolar, iP, iHmolar);
3770 CoolPropDbl d2rhodhdp_liq = Liq->second_partial_deriv(iDmolar, iHmolar, iP, iP, iHmolar); // ?
3771
3772 // Derivatives at the end point
3773 // CoolPropDbl drho_dp__consth_end = End->calc_first_two_phase_deriv(iDmolar, iP, iHmolar);
3774 CoolPropDbl d2rhodhdp_end = End->calc_second_two_phase_deriv(iDmolar, iHmolar, iP, iP, iHmolar);
3775
3776 // Reminder:
3777 // Delta = Q()*(hV-hL) = h-hL
3778 // Delta_end = x_end*(hV-hL);
3779 CoolPropDbl d_Delta_dp__consth = -dhL_dp_sat;
3780 CoolPropDbl d_Delta_end_dp__consth = x_end * (dhV_dp_sat - dhL_dp_sat);
3781
3782 // First pressure derivative at constant h of the coefficients a,b,c,d
3783 // CoolPropDbl Abracket = (2*rho_liq - 2*rho_end + Delta_end * (drho_dh_liq__constp + drho_dh_end));
3784 CoolPropDbl d_Abracket_dp_consth = (2 * drhoL_dp_sat - 2 * drho_dp_end + Delta_end * (d2rhodhdp_liq + d2rhodhdp_end)
3785 + d_Delta_end_dp__consth * (drho_dh_liq__constp + drho_dh_end));
3786 CoolPropDbl da_dp = 1 / POW3(Delta_end) * d_Abracket_dp_consth + Abracket * (-3 / POW4(Delta_end) * d_Delta_end_dp__consth);
3787 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)
3788 + (1 / POW2(Delta_end) * d_Delta_end_dp__consth) * (drho_dh_end + 2 * drho_dh_liq__constp)
3789 - (1 / Delta_end) * (d2rhodhdp_end + 2 * d2rhodhdp_liq);
3790 CoolPropDbl dc_dp = d2rhodhdp_liq;
3791 CoolPropDbl dd_dp = drhoL_dp_sat;
3792
3794 (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;
3795 if (drho_dp__h) return _drho_spline_dp__consth;
3796
3797 throw ValueError("Something went wrong in HelmholtzEOSMixtureBackend::calc_first_two_phase_deriv_splined");
3798 return _HUGE;
3799}
3800
3802 class Resid : public FuncWrapperND
3803 {
3804 public:
3806 double L1, M1;
3807 Eigen::MatrixXd Lstar, Mstar;
3808 Resid(HelmholtzEOSMixtureBackend& HEOS) : HEOS(HEOS), L1(_HUGE), M1(_HUGE) {};
3809 std::vector<double> call(const std::vector<double>& tau_delta) {
3810 double rhomolar = tau_delta[1] * HEOS.rhomolar_reducing();
3811 double T = HEOS.T_reducing() / tau_delta[0];
3814 Mstar = MixtureDerivatives::Mstar(HEOS, XN_INDEPENDENT, Lstar);
3815 std::vector<double> o(2);
3816 o[0] = Lstar.determinant();
3817 o[1] = Mstar.determinant();
3818 return o;
3819 };
3820 std::vector<std::vector<double>> Jacobian(const std::vector<double>& x) {
3821 std::size_t N = x.size();
3822 std::vector<std::vector<double>> J(N, std::vector<double>(N, 0));
3823 Eigen::MatrixXd adjL = adjugate(Lstar), adjM = adjugate(Mstar), dLdTau = MixtureDerivatives::dLstar_dX(HEOS, XN_INDEPENDENT, iTau),
3825 dMdTau = MixtureDerivatives::dMstar_dX(HEOS, XN_INDEPENDENT, iTau, Lstar, dLdTau),
3826 dMdDelta = MixtureDerivatives::dMstar_dX(HEOS, XN_INDEPENDENT, iDelta, Lstar, dLdDelta);
3827
3828 J[0][0] = (adjL * dLdTau).trace();
3829 J[0][1] = (adjL * dLdDelta).trace();
3830 J[1][0] = (adjM * dMdTau).trace();
3831 J[1][1] = (adjM * dMdDelta).trace();
3832 return J;
3833 }
3835 std::vector<std::vector<double>> numerical_Jacobian(const std::vector<double>& x) {
3836 std::size_t N = x.size();
3837 std::vector<double> rplus, rminus, xp;
3838 std::vector<std::vector<double>> J(N, std::vector<double>(N, 0));
3839
3840 double epsilon = 0.0001;
3841
3842 // Build the Jacobian by column
3843 for (std::size_t i = 0; i < N; ++i) {
3844 xp = x;
3845 xp[i] += epsilon;
3846 rplus = call(xp);
3847 xp[i] -= 2 * epsilon;
3848 rminus = call(xp);
3849
3850 for (std::size_t j = 0; j < N; ++j) {
3851 J[j][i] = (rplus[j] - rminus[j]) / (2 * epsilon);
3852 }
3853 }
3854 std::cout << J[0][0] << " " << J[0][1] << std::endl;
3855 std::cout << J[1][0] << " " << J[1][1] << std::endl;
3856 return J;
3857 };
3858 };
3859 Resid resid(*this);
3860 std::vector<double> x, tau_delta(2);
3861 tau_delta[0] = T_reducing() / T0;
3862 tau_delta[1] = rho0 / rhomolar_reducing();
3863 x = NDNewtonRaphson_Jacobian(&resid, tau_delta, 1e-10, 100);
3864 _critical.T = T_reducing() / x[0];
3867
3868 CriticalState critical;
3869 critical.T = _critical.T;
3870 critical.p = _critical.p;
3871 critical.rhomolar = _critical.rhomolar;
3872 if (_critical.p < 0) {
3873 critical.stable = false;
3874 } else {
3875 if (get_config_bool(ASSUME_CRITICAL_POINT_STABLE)) {
3876 critical.stable = true;
3877 } else {
3878 // Otherwise we try to check stability with TPD-based analysis
3879 StabilityRoutines::StabilityEvaluationClass stability_tester(*this);
3880 critical.stable = stability_tester.is_stable();
3881 }
3882 }
3883 return critical;
3884}
3885
3890{
3891 public:
3893 const double delta;
3896 : HEOS(HEOS), delta(delta0), _call(_HUGE), _deriv(_HUGE), _second_deriv(_HUGE) {};
3897 double call(double tau) {
3898 double rhomolar = HEOS.rhomolar_reducing() * delta, T = HEOS.T_reducing() / tau;
3899 HEOS.update_DmolarT_direct(rhomolar, T);
3901 return _call;
3902 }
3903 double deriv(double tau) {
3904 Eigen::MatrixXd adjL = adjugate(MixtureDerivatives::Lstar(HEOS, XN_INDEPENDENT)),
3906 _deriv = (adjL * dLdTau).trace();
3907 return _deriv;
3908 };
3909 double second_deriv(double tau) {
3910 Eigen::MatrixXd Lstar = MixtureDerivatives::Lstar(HEOS, XN_INDEPENDENT),
3912 d2LstardTau2 = MixtureDerivatives::d2Lstar_dX2(HEOS, XN_INDEPENDENT, iTau, iTau), adjL = adjugate(Lstar),
3913 dadjLstardTau = adjugate_derivative(Lstar, dLstardTau);
3914 _second_deriv = (dLstardTau * dadjLstardTau + adjL * d2LstardTau2).trace();
3915 return _second_deriv;
3916 };
3917};
3918
3922{
3923 public:
3925 double delta, tau,
3932 std::vector<CoolProp::CriticalState> critical_points;
3936 bool
3938 L0CurveTracer(HelmholtzEOSMixtureBackend& HEOS, double tau0, double delta0)
3939 : HEOS(HEOS), delta(delta0), tau(tau0), M1_last(_HUGE), N_critical_points(0), find_critical_points(true) {
3940 R_delta_tracer = 0.1;
3942 R_tau_tracer = 0.1;
3944 };
3945 /***
3946 \brief Update values for tau, delta
3947 @param theta The angle
3948 @param tau The old value of tau
3949 @param delta The old value of delta
3950 @param tau_new The new value of tau
3951 @param delta_new The new value of delta
3952 */
3953 void get_tau_delta(const double theta, const double tau, const double delta, double& tau_new, double& delta_new) {
3954 tau_new = tau + R_tau * cos(theta);
3955 delta_new = delta + R_delta * sin(theta);
3956 };
3957 /***
3958 \brief Calculate the value of L1
3959 @param theta The angle
3960 */
3961 double call(double theta) {
3962 double tau_new, delta_new;
3963 this->get_tau_delta(theta, tau, delta, tau_new, delta_new);
3964 double rhomolar = HEOS.rhomolar_reducing() * delta_new, T = HEOS.T_reducing() / tau_new;
3965 HEOS.update_DmolarT_direct(rhomolar, T);
3967 adjLstar = adjugate(Lstar);
3970 double L1 = Lstar.determinant();
3971 return L1;
3972 };
3973 /***
3974 \brief Calculate the first partial derivative of L1 with respect to the angle
3975 @param theta The angle
3976 */
3977 double deriv(double theta) {
3978 double dL1_dtau = (adjLstar * dLstardTau).trace(), dL1_ddelta = (adjLstar * dLstardDelta).trace();
3979 return -R_tau * sin(theta) * dL1_dtau + R_delta * cos(theta) * dL1_ddelta;
3980 };
3981
3982 void trace() {
3983 bool debug = (get_debug_level() > 0) | false;
3984 double theta;
3985 for (int i = 0; i < 300; ++i) {
3986 if (i == 0) {
3987 // In the first iteration, search all angles in the positive delta direction using a
3988 // bounded solver with a very small radius in order to not hit other L1*=0 contours
3989 // that are in the vicinity
3990 R_tau = 0.001;
3991 R_delta = 0.001;
3992 theta = Brent(this, 0, M_PI, DBL_EPSILON, 1e-10, 100);
3993 } else {
3994 // In subsequent iterations, you already have an excellent guess for the direction to
3995 // be searching in, use Newton's method to refine the solution since we also
3996 // have an analytic solution for the derivative
3999 try {
4000 theta = Newton(this, theta_last, 1e-10, 100);
4001 } catch (...) {
4002 if (N_critical_points > 0 && delta > 1.5 * critical_points[0].rhomolar / HEOS.rhomolar_reducing()) {
4003 // Stopping the search - probably we have a kink in the L1*=0 contour
4004 // caused by poor low-temperature behavior
4005 continue;
4006 }
4007 }
4008
4009 // If the solver takes a U-turn, going in the opposite direction of travel
4010 // this is not a good thing, and force a Brent's method solver call to make sure we keep
4011 // tracing in the same direction
4012 if (std::abs(angle_difference(theta, theta_last)) > M_PI / 2.0) {
4013 // We have found at least one critical point and we are now well above the density
4014 // associated with the first critical point
4015 if (N_critical_points > 0 && delta > 1.2 * critical_points[0].rhomolar / HEOS.rhomolar_reducing()) {
4016 // Stopping the search - probably we have a kink in the L1*=0 contour
4017 // caused by poor low-temperature behavior
4018 continue;
4019 } else {
4020 theta = Brent(this, theta_last - M_PI / 3.5, theta_last + M_PI / 3.5, DBL_EPSILON, 1e-10, 100);
4021 }
4022 }
4023 }
4024
4025 // Calculate the second criticality condition
4026 double M1 = MixtureDerivatives::Mstar(HEOS, XN_INDEPENDENT, Lstar).determinant();
4027 double p_MPa = HEOS.p() / 1e6;
4028
4029 // Calculate the new tau and delta at the new point
4030 double tau_new, delta_new;
4031 this->get_tau_delta(theta, tau, delta, tau_new, delta_new);
4032
4033 // Stop if bounds are exceeded
4034 if (p_MPa > 500 || HEOS.get_critical_is_terminated(delta_new, tau_new)) {
4035 break;
4036 }
4037
4038 // If the sign of M1 and the previous value of M1 have different signs, it means that
4039 // you have bracketed a critical point, run the full critical point solver to
4040 // find the critical point and store it
4041 // Only enabled if find_critical_points is true (the default)
4042 if (i > 0 && M1 * M1_last < 0 && find_critical_points) {
4043 double rhomolar = HEOS.rhomolar_reducing() * (delta + delta_new) / 2.0, T = HEOS.T_reducing() / ((tau + tau_new) / 2.0);
4045 critical_points.push_back(crit);
4047 if (debug) {
4048 std::cout << HEOS.get_mole_fractions()[0] << " " << crit.rhomolar << " " << crit.T << " " << p_MPa << std::endl;
4049 }
4050 }
4051
4052 // Update the storage values
4053 this->tau = tau_new;
4054 this->delta = delta_new;
4055 this->M1_last = M1;
4056 this->theta_last = theta;
4057
4058 this->spinodal_values.tau.push_back(tau_new);
4059 this->spinodal_values.delta.push_back(delta_new);
4060 this->spinodal_values.M1.push_back(M1);
4061 }
4062 };
4063};
4064
4066 Eigen::MatrixXd Lstar = MixtureDerivatives::Lstar(*this, XN_INDEPENDENT);
4067 Eigen::MatrixXd Mstar = MixtureDerivatives::Mstar(*this, XN_INDEPENDENT, Lstar);
4068 L1star = Lstar.determinant();
4069 M1star = Mstar.determinant();
4070};
4071
4073 R_delta = 0.025;
4074 R_tau = 0.1;
4075}
4076std::vector<CoolProp::CriticalState> HelmholtzEOSMixtureBackend::_calc_all_critical_points(bool find_critical_points) {
4077 // Populate the temporary class used to calculate the critical point(s)
4079 if (get_debug_level() > 10) {
4080 rapidjson::Document doc;
4081 doc.SetObject();
4082 rapidjson::Value& val = doc;
4083 std::vector<std::vector<DepartureFunctionPointer>>& mat = critical_state->residual_helmholtz->Excess.DepartureFunctionMatrix;
4084 if (mat.size() > 0) {
4085 mat[0][1]->phi.to_json(val, doc);
4086 std::cout << cpjson::to_string(doc);
4087 }
4088 }
4089 critical_state->set_mole_fractions(this->get_mole_fractions_ref());
4090
4091 // Specify state to be something homogeneous to shortcut phase evaluation
4092 critical_state->specify_phase(iphase_gas);
4093
4094 double delta0 = _HUGE, tau0 = _HUGE;
4095 critical_state->get_critical_point_starting_values(delta0, tau0);
4096
4097 OneDimObjective resid_L0(*critical_state, delta0);
4098
4099 // If the derivative of L1star with respect to tau is positive,
4100 // tau needs to be increased such that we sit on the other
4101 // side of the d(L1star)/dtau = 0 contour
4102 resid_L0.call(tau0);
4103 int bump_count = 0;
4104 while (resid_L0.deriv(tau0) > 0 && bump_count < 3) {
4105 tau0 *= 1.1;
4106 bump_count++;
4107 }
4108 double tau_L0 = Halley(resid_L0, tau0, 1e-10, 100);
4109 //double T0 = T_reducing()/tau_L0;
4110 //double rho0 = delta0*rhomolar_reducing();
4111
4112 L0CurveTracer tracer(*critical_state, tau_L0, delta0);
4113 tracer.find_critical_points = find_critical_points;
4114
4115 double R_delta = 0, R_tau = 0;
4116 critical_state->get_critical_point_search_radii(R_delta, R_tau);
4117 tracer.R_delta_tracer = R_delta;
4118 tracer.R_tau_tracer = R_tau;
4119 tracer.trace();
4120
4121 this->spinodal_values = tracer.spinodal_values;
4122
4123 return tracer.critical_points;
4124}
4125
4126double HelmholtzEOSMixtureBackend::calc_tangent_plane_distance(const double T, const double p, const std::vector<double>& w,
4127 const double rhomolar_guess) {
4128
4129 const std::vector<CoolPropDbl>& z = this->get_mole_fractions_ref();
4130 if (w.size() != z.size()) {
4131 throw ValueError(format("Trial composition vector size [%d] is not the same as bulk composition [%d]", w.size(), z.size()));
4132 }
4133 add_TPD_state();
4134 TPD_state->set_mole_fractions(w);
4135
4136 CoolPropDbl rho = TPD_state->solver_rho_Tp_global(T, p, 0.9 / TPD_state->SRK_covolume());
4137 TPD_state->update_DmolarT_direct(rho, T);
4138
4139 double summer = 0;
4140 for (std::size_t i = 0; i < w.size(); ++i) {
4141 summer +=
4143 }
4144 return summer;
4145}
4146
4148 // Ok, we are faking a little bit here, hijacking the code for critical points, but skipping evaluation of critical points
4149 bool find_critical_points = false;
4150 _calc_all_critical_points(find_critical_points);
4151}
4152
4153void HelmholtzEOSMixtureBackend::set_reference_stateS(const std::string& reference_state) {
4154 for (std::size_t i = 0; i < components.size(); ++i) {
4155 CoolProp::HelmholtzEOSMixtureBackend HEOS(std::vector<CoolPropFluid>(1, components[i]));
4156 if (!reference_state.compare("IIR")) {
4157 if (HEOS.Ttriple() > 273.15) {
4158 throw ValueError(format("Cannot use IIR reference state; Ttriple [%Lg] is greater than 273.15 K", HEOS.Ttriple()));
4159 }
4160 HEOS.update(QT_INPUTS, 0, 273.15);
4161
4162 // Get current values for the enthalpy and entropy
4163 double deltah = HEOS.hmass() - 200000; // offset from 200000 J/kg enthalpy
4164 double deltas = HEOS.smass() - 1000; // offset from 1000 J/kg/K entropy
4165 double delta_a1 = deltas / (HEOS.gas_constant() / HEOS.molar_mass());
4166 double delta_a2 = -deltah / (HEOS.gas_constant() / HEOS.molar_mass() * HEOS.get_reducing_state().T);
4167 // Change the value in the library for the given fluid
4168 set_fluid_enthalpy_entropy_offset(components[i], delta_a1, delta_a2, "IIR");
4169 if (get_debug_level() > 0) {
4170 std::cout << format("set offsets to %0.15g and %0.15g\n", delta_a1, delta_a2);
4171 }
4172 } else if (!reference_state.compare("ASHRAE")) {
4173 if (HEOS.Ttriple() > 233.15) {
4174 throw ValueError(format("Cannot use ASHRAE reference state; Ttriple [%Lg] is greater than than 233.15 K", HEOS.Ttriple()));
4175 }
4176 HEOS.update(QT_INPUTS, 0, 233.15);
4177
4178 // Get current values for the enthalpy and entropy
4179 double deltah = HEOS.hmass() - 0; // offset from 0 J/kg enthalpy
4180 double deltas = HEOS.smass() - 0; // offset from 0 J/kg/K entropy
4181 double delta_a1 = deltas / (HEOS.gas_constant() / HEOS.molar_mass());
4182 double delta_a2 = -deltah / (HEOS.gas_constant() / HEOS.molar_mass() * HEOS.get_reducing_state().T);
4183 // Change the value in the library for the given fluid
4184 set_fluid_enthalpy_entropy_offset(components[i], delta_a1, delta_a2, "ASHRAE");
4185 if (get_debug_level() > 0) {
4186 std::cout << format("set offsets to %0.15g and %0.15g\n", delta_a1, delta_a2);
4187 }
4188 } else if (!reference_state.compare("NBP")) {
4189 if (HEOS.p_triple() > 101325) {
4190 throw ValueError(format("Cannot use NBP reference state; p_triple [%Lg Pa] is greater than than 101325 Pa", HEOS.p_triple()));
4191 }
4192 // Saturated liquid boiling point at 1 atmosphere
4193 HEOS.update(PQ_INPUTS, 101325, 0);
4194
4195 double deltah = HEOS.hmass() - 0; // offset from 0 kJ/kg enthalpy
4196 double deltas = HEOS.smass() - 0; // offset from 0 kJ/kg/K entropy
4197 double delta_a1 = deltas / (HEOS.gas_constant() / HEOS.molar_mass());
4198 double delta_a2 = -deltah / (HEOS.gas_constant() / HEOS.molar_mass() * HEOS.get_reducing_state().T);
4199 // Change the value in the library for the given fluid
4200 set_fluid_enthalpy_entropy_offset(components[i], delta_a1, delta_a2, "NBP");
4201 if (get_debug_level() > 0) {
4202 std::cout << format("set offsets to %0.15g and %0.15g\n", delta_a1, delta_a2);
4203 }
4204 } else if (!reference_state.compare("DEF")) {
4206 } else if (!reference_state.compare("RESET")) {
4208 } else {
4209 throw ValueError(format("reference state string is invalid: [%s]", reference_state.c_str()));
4210 }
4211 }
4212}
4213
4219void HelmholtzEOSMixtureBackend::set_reference_stateD(double T, double rhomolar, double hmolar0, double smolar0) {
4220 for (std::size_t i = 0; i < components.size(); ++i) {
4221 CoolProp::HelmholtzEOSMixtureBackend HEOS(std::vector<CoolPropFluid>(1, components[i]));
4222
4223 // Skip the cache and phase calculation because we are given a temperature and density directly;
4224 // just evaluate the EOS
4227
4228 // Get current values for the enthalpy and entropy
4229 double deltah = hmolar - hmolar0; // offset from specified enthalpy in J/mol
4230 double deltas = smolar - smolar0; // offset from specified entropy in J/mol/K
4231 double delta_a1 = deltas / (HEOS.gas_constant());
4232 double delta_a2 = -deltah / (HEOS.gas_constant() * HEOS.get_reducing_state().T);
4233 set_fluid_enthalpy_entropy_offset(components[i], delta_a1, delta_a2, "custom");
4234 }
4235}
4236
4238 const std::string& ref) {
4239 component.EOS().alpha0.EnthalpyEntropyOffset.set(delta_a1, delta_a2, ref);
4240
4241 shared_ptr<CoolProp::HelmholtzEOSBackend> HEOS(new CoolProp::HelmholtzEOSBackend(component));
4242 HEOS->specify_phase(iphase_gas); // Something homogeneous;
4243 // Calculate the new enthalpy and entropy values
4244 HEOS->update(DmolarT_INPUTS, component.EOS().hs_anchor.rhomolar, component.EOS().hs_anchor.T);
4245 component.EOS().hs_anchor.hmolar = HEOS->hmolar();
4246 component.EOS().hs_anchor.smolar = HEOS->smolar();
4247
4248 double f = (HEOS->name() == "Water" || HEOS->name() == "CarbonDioxide") ? 1.00001 : 1.0;
4249
4250 // Calculate the new enthalpy and entropy values at the reducing state
4251 HEOS->update(DmolarT_INPUTS, component.EOS().reduce.rhomolar * f, component.EOS().reduce.T * f);
4252 component.EOS().reduce.hmolar = HEOS->hmolar();
4253 component.EOS().reduce.smolar = HEOS->smolar();
4254
4255 // Calculate the new enthalpy and entropy values at the critical state
4256 HEOS->update(DmolarT_INPUTS, component.crit.rhomolar * f, component.crit.T * f);
4257 component.crit.hmolar = HEOS->hmolar();
4258 component.crit.smolar = HEOS->smolar();
4259
4260 // Calculate the new enthalpy and entropy values
4261 HEOS->update(DmolarT_INPUTS, component.triple_liquid.rhomolar, component.triple_liquid.T);
4262 component.triple_liquid.hmolar = HEOS->hmolar();
4263 component.triple_liquid.smolar = HEOS->smolar();
4264
4265 // Calculate the new enthalpy and entropy values
4266 HEOS->update(DmolarT_INPUTS, component.triple_vapor.rhomolar, component.triple_vapor.T);
4267 component.triple_vapor.hmolar = HEOS->hmolar();
4268 component.triple_vapor.smolar = HEOS->smolar();
4269
4270 if (!HEOS->is_pure()) {
4271 // Calculate the new enthalpy and entropy values
4272 HEOS->update(DmolarT_INPUTS, component.EOS().max_sat_T.rhomolar, component.EOS().max_sat_T.T);
4273 component.EOS().max_sat_T.hmolar = HEOS->hmolar();
4274 component.EOS().max_sat_T.smolar = HEOS->smolar();
4275 // Calculate the new enthalpy and entropy values
4276 HEOS->update(DmolarT_INPUTS, component.EOS().max_sat_p.rhomolar, component.EOS().max_sat_p.T);
4277 component.EOS().max_sat_p.hmolar = HEOS->hmolar();
4278 component.EOS().max_sat_p.smolar = HEOS->smolar();
4279 }
4280}
4281
4282} /* namespace CoolProp */