CoolProp 6.8.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));
103 } else {
104 // Set the mixture parameters - binary pair reducing functions, departure functions, F_ij, etc.
106 }
107
109
110 // Top-level class can hold copies of the base saturation classes,
111 // saturation classes cannot hold copies of the saturation classes
112 if (generate_SatL_and_SatV) {
113 SatL.reset(get_copy(false));
114 SatL->specify_phase(iphase_liquid);
115 linked_states.push_back(SatL);
116 SatV.reset(get_copy(false));
117 SatV->specify_phase(iphase_gas);
118 linked_states.push_back(SatV);
119 }
120}
121void HelmholtzEOSMixtureBackend::set_mole_fractions(const std::vector<CoolPropDbl>& mf) {
122 if (mf.size() != N) {
123 throw ValueError(format("size of mole fraction vector [%d] does not equal that of component vector [%d]", mf.size(), N));
124 }
125 // Copy values without reallocating memory
126 this->mole_fractions = mf; // Most effective copy
127 this->resize(N); // No reallocation of this->mole_fractions happens
129};
131 residual_helmholtz.reset(source->residual_helmholtz->copy_ptr());
132 if (source->Reducing) {
133 Reducing.reset(source->Reducing->copy());
134 }
135 // Recurse into linked states of the class
136 for (std::vector<shared_ptr<HelmholtzEOSMixtureBackend>>::iterator it = linked_states.begin(); it != linked_states.end(); ++it) {
137 it->get()->sync_linked_states(source);
138 }
139}
141 // Set up the class with these components
142 HelmholtzEOSMixtureBackend* ptr = new HelmholtzEOSMixtureBackend(components, generate_SatL_and_SatV);
143 // Recursively walk into linked states, setting the departure and reducing terms
144 // to be equal to the parent (this instance)
145 ptr->sync_linked_states(this);
146 return ptr;
147};
148void HelmholtzEOSMixtureBackend::set_mass_fractions(const std::vector<CoolPropDbl>& mass_fractions) {
149 if (mass_fractions.size() != N) {
150 throw ValueError(format("size of mass fraction vector [%d] does not equal that of component vector [%d]", mass_fractions.size(), N));
151 }
152 std::vector<CoolPropDbl> moles;
153 CoolPropDbl sum_moles = 0.0;
154 CoolPropDbl tmp = 0.0;
155 for (unsigned int i = 0; i < components.size(); ++i) {
156 tmp = mass_fractions[i] / components[i].molar_mass();
157 moles.push_back(tmp);
158 sum_moles += tmp;
159 }
160 std::vector<CoolPropDbl> mole_fractions;
161 for (std::vector<CoolPropDbl>::iterator it = moles.begin(); it != moles.end(); ++it) {
162 mole_fractions.push_back(*it / sum_moles);
163 }
164 this->set_mole_fractions(mole_fractions);
165};
167 this->mole_fractions.resize(N);
168 this->K.resize(N);
169 this->lnK.resize(N);
170 for (std::vector<shared_ptr<HelmholtzEOSMixtureBackend>>::iterator it = linked_states.begin(); it != linked_states.end(); ++it) {
171 it->get()->N = N;
172 it->get()->resize(N);
173 }
174}
176 if (p() > p_critical()) {
177 if (T() > T_critical()) {
179 } else {
181 }
182 } else {
183 if (T() > T_critical()) {
185 } else {
186 // Liquid or vapor
187 if (rhomolar() > rhomolar_critical()) {
189 } else {
191 }
192 }
193 }
194}
195std::string HelmholtzEOSMixtureBackend::fluid_param_string(const std::string& ParamName) {
197 if (!ParamName.compare("name")) {
198 return cpfluid.name;
199 } else if (!ParamName.compare("aliases")) {
200 return strjoin(cpfluid.aliases, get_config_string(LIST_STRING_DELIMITER));
201 } else if (!ParamName.compare("CAS") || !ParamName.compare("CAS_number")) {
202 return cpfluid.CAS;
203 } else if (!ParamName.compare("formula")) {
204 return cpfluid.formula;
205 } else if (!ParamName.compare("ASHRAE34")) {
206 return cpfluid.environment.ASHRAE34;
207 } else if (!ParamName.compare("REFPROPName") || !ParamName.compare("REFPROP_name") || !ParamName.compare("REFPROPname")) {
208 return cpfluid.REFPROPname;
209 } else if (ParamName.find("BibTeX") == 0) // Starts with "BibTeX"
210 {
211 std::vector<std::string> parts = strsplit(ParamName, '-');
212 if (parts.size() != 2) {
213 throw ValueError(format("Unable to parse BibTeX string %s", ParamName.c_str()));
214 }
215 std::string key = parts[1];
216 if (!key.compare("EOS")) {
217 return cpfluid.EOS().BibTeX_EOS;
218 } else if (!key.compare("CP0")) {
219 return cpfluid.EOS().BibTeX_CP0;
220 } else if (!key.compare("VISCOSITY")) {
221 return cpfluid.transport.BibTeX_viscosity;
222 } else if (!key.compare("CONDUCTIVITY")) {
223 return cpfluid.transport.BibTeX_conductivity;
224 } else if (!key.compare("ECS_LENNARD_JONES")) {
225 throw NotImplementedError();
226 } else if (!key.compare("ECS_VISCOSITY_FITS")) {
227 throw NotImplementedError();
228 } else if (!key.compare("ECS_CONDUCTIVITY_FITS")) {
229 throw NotImplementedError();
230 } else if (!key.compare("SURFACE_TENSION")) {
231 return cpfluid.ancillaries.surface_tension.BibTeX;
232 } else if (!key.compare("MELTING_LINE")) {
233 return cpfluid.ancillaries.melting_line.BibTeX;
234 } else {
235 throw CoolProp::KeyError(format("Bad key to get_BibTeXKey [%s]", key.c_str()));
236 }
237 } else if (ParamName.find("pure") == 0) {
238 if (is_pure()) {
239 return "true";
240 } else {
241 return "false";
242 }
243 } else if (ParamName == "INCHI" || ParamName == "InChI" || ParamName == "INCHI_STRING") {
244 return cpfluid.InChI;
245 } else if (ParamName == "INCHI_Key" || ParamName == "InChIKey" || ParamName == "INCHIKEY") {
246 return cpfluid.InChIKey;
247 } else if (ParamName == "2DPNG_URL") {
248 return cpfluid.TwoDPNG_URL;
249 } else if (ParamName == "SMILES" || ParamName == "smiles") {
250 return cpfluid.smiles;
251 } else if (ParamName == "CHEMSPIDER_ID") {
252 return format("%d", cpfluid.ChemSpider_id);
253 } else if (ParamName == "JSON") {
254 return get_fluid_as_JSONstring(cpfluid.CAS);
255 } else {
256 throw ValueError(format("fluid parameter [%s] is invalid", ParamName.c_str()));
257 }
258}
259
260void HelmholtzEOSMixtureBackend::apply_simple_mixing_rule(std::size_t i, std::size_t j, const std::string& model) {
261 // bound-check indices
262 if (i < 0 || i >= N) {
263 if (j < 0 || j >= N) {
264 throw ValueError(format("Both indices i [%d] and j [%d] are out of bounds. Must be between 0 and %d.", i, j, N-1));
265 } else {
266 throw ValueError(format("Index i [%d] is out of bounds. Must be between 0 and %d.", i, N-1));
267 }
268 } else if (j < 0 || j >= N) {
269 throw ValueError(format("Index j [%d] is out of bounds. Must be between 0 and %d.", j, N-1));
270 }
271 if (model == "linear") {
273 double gammaT = 0.5 * (Tc1 + Tc2) / sqrt(Tc1 * Tc2);
275 double gammaV = 4.0 * (1 / rhoc1 + 1 / rhoc2) / pow(pow(rhoc1, -1.0 / 3.0) + pow(rhoc2, -1.0 / 3.0), 3);
276 set_binary_interaction_double(i, j, "betaT", 1.0);
277 set_binary_interaction_double(i, j, "gammaT", gammaT);
278 set_binary_interaction_double(i, j, "betaV", 1.0);
279 set_binary_interaction_double(i, j, "gammaV", gammaV);
280 } else if (model == "Lorentz-Berthelot") {
281 set_binary_interaction_double(i, j, "betaT", 1.0);
282 set_binary_interaction_double(i, j, "gammaT", 1.0);
283 set_binary_interaction_double(i, j, "betaV", 1.0);
284 set_binary_interaction_double(i, j, "gammaV", 1.0);
285 } else {
286 throw ValueError(format("mixing rule [%s] is not understood", model.c_str()));
287 }
288}
290void HelmholtzEOSMixtureBackend::set_binary_interaction_double(const std::size_t i, const std::size_t j, const std::string& parameter,
291 const double value) {
292 // bound-check indices
293 if (i < 0 || i >= N) {
294 if (j < 0 || j >= N) {
295 throw ValueError(format("Both indices i [%d] and j [%d] are out of bounds. Must be between 0 and %d.", i, j, N-1));
296 } else {
297 throw ValueError(format("Index i [%d] is out of bounds. Must be between 0 and %d.", i, N-1));
298 }
299 } else if (j < 0 || j >= N) {
300 throw ValueError(format("Index j [%d] is out of bounds. Must be between 0 and %d.", j, N-1));
301 }
302 if (parameter == "Fij") {
303 residual_helmholtz->Excess.F[i][j] = value;
304 residual_helmholtz->Excess.F[j][i] = value;
305 } else {
306 Reducing->set_binary_interaction_double(i, j, parameter, value);
307 }
309 for (std::vector<shared_ptr<HelmholtzEOSMixtureBackend>>::iterator it = linked_states.begin(); it != linked_states.end(); ++it) {
310 it->get()->set_binary_interaction_double(i, j, parameter, value);
311 }
312};
314double HelmholtzEOSMixtureBackend::get_binary_interaction_double(const std::size_t i, const std::size_t j, const std::string& parameter) {
315 // bound-check indices
316 if (i < 0 || i >= N) {
317 if (j < 0 || j >= N) {
318 throw ValueError(format("Both indices i [%d] and j [%d] are out of bounds. Must be between 0 and %d.", i, j, N-1));
319 } else {
320 throw ValueError(format("Index i [%d] is out of bounds. Must be between 0 and %d.", i, N-1));
321 }
322 } else if (j < 0 || j >= N) {
323 throw ValueError(format("Index j [%d] is out of bounds. Must be between 0 and %d.", j, N-1));
324 }
325 if (parameter == "Fij") {
326 return residual_helmholtz->Excess.F[i][j];
327 } else {
328 return Reducing->get_binary_interaction_double(i, j, parameter);
329 }
330};
332//std::string HelmholtzEOSMixtureBackend::get_binary_interaction_string(const std::string &CAS1, const std::string &CAS2, const std::string &parameter){
333// return CoolProp::get_mixture_binary_pair_data(CAS1, CAS2, parameter);
334//}
336void HelmholtzEOSMixtureBackend::set_binary_interaction_string(const std::size_t i, const std::size_t j, const std::string& parameter,
337 const std::string& value) {
338 // bound-check indices
339 if (i < 0 || i >= N) {
340 if (j < 0 || j >= N) {
341 throw ValueError(format("Both indices i [%d] and j [%d] are out of bounds. Must be between 0 and %d.", i, j, N-1));
342 } else {
343 throw ValueError(format("Index i [%d] is out of bounds. Must be between 0 and %d.", i, N-1));
344 }
345 } else if (j < 0 || j >= N) {
346 throw ValueError(format("Index j [%d] is out of bounds. Must be between 0 and %d.", j, N-1));
347 }
348 if (parameter == "function") {
349 residual_helmholtz->Excess.DepartureFunctionMatrix[i][j].reset(get_departure_function(value));
350 residual_helmholtz->Excess.DepartureFunctionMatrix[j][i].reset(get_departure_function(value));
351 } else {
352 throw ValueError(format("Cannot process this string parameter [%s] in set_binary_interaction_string", parameter.c_str()));
353 }
355 for (std::vector<shared_ptr<HelmholtzEOSMixtureBackend>>::iterator it = linked_states.begin(); it != linked_states.end(); ++it) {
356 it->get()->set_binary_interaction_string(i, j, parameter, value);
357 }
358};
359
360void HelmholtzEOSMixtureBackend::calc_change_EOS(const std::size_t i, const std::string& EOS_name) {
361
362 if (i < components.size()) {
363 CoolPropFluid& fluid = components[i];
364 EquationOfState& EOS = fluid.EOSVector[0];
365
366 if (EOS_name == "SRK" || EOS_name == "Peng-Robinson") {
367
368 // Get the parameters for the cubic EOS
369 CoolPropDbl Tc = EOS.reduce.T;
370 CoolPropDbl pc = EOS.reduce.p;
371 CoolPropDbl rhomolarc = EOS.reduce.rhomolar;
372 CoolPropDbl acentric = EOS.acentric;
373 CoolPropDbl R = 8.3144598;
374
375 // Remove the residual part
376 EOS.alphar.empty_the_EOS();
377 // Set the contribution
378 shared_ptr<AbstractCubic> ac;
379 if (EOS_name == "SRK") {
380 ac.reset(new SRK(Tc, pc, acentric, R));
381 } else {
382 ac.reset(new PengRobinson(Tc, pc, acentric, R));
383 }
384 ac->set_Tr(Tc);
385 ac->set_rhor(rhomolarc);
387 } else if (EOS_name == "XiangDeiters") {
388
389 // Get the parameters for the EOS
390 CoolPropDbl Tc = EOS.reduce.T;
391 CoolPropDbl pc = EOS.reduce.p;
392 CoolPropDbl rhomolarc = EOS.reduce.rhomolar;
393 CoolPropDbl acentric = EOS.acentric;
394 CoolPropDbl R = 8.3144598;
395
396 // Remove the residual part
397 EOS.alphar.empty_the_EOS();
398 // Set the Xiang & Deiters contribution
399 EOS.alphar.XiangDeiters = ResidualHelmholtzXiangDeiters(Tc, pc, rhomolarc, acentric, R);
400 }
401 } else {
402 throw ValueError(format("Index [%d] is invalid", i));
403 }
404 // Now do the same thing to the saturated liquid and vapor instances if possible
405 if (this->SatL) SatL->change_EOS(i, EOS_name);
406 if (this->SatV) SatV->change_EOS(i, EOS_name);
407}
409 // Clear the phase envelope data
411 // Build the phase envelope
412 PhaseEnvelopeRoutines::build(*this, type);
413 // Finalize the phase envelope
415};
417 // Build the matrix of binary-pair reducing functions
419}
421 CoolPropFluid& component = components[0];
422 EquationOfState& EOS = component.EOSVector[0];
423
424 // Clear the state class
425 clear();
426
427 // Calculate the new enthalpy and entropy values
429 EOS.hs_anchor.hmolar = hmolar();
430 EOS.hs_anchor.smolar = smolar();
431
432 // Calculate the new enthalpy and entropy values at the reducing state
434 EOS.reduce.hmolar = hmolar();
435 EOS.reduce.smolar = smolar();
436
437 // Clear again just to be sure
438 clear();
439}
442 if (!state.compare("hs_anchor")) {
443 return components[0].EOS().hs_anchor;
444 } else if (!state.compare("max_sat_T")) {
445 return components[0].EOS().max_sat_T;
446 } else if (!state.compare("max_sat_p")) {
447 return components[0].EOS().max_sat_p;
448 } else if (!state.compare("reducing")) {
449 return components[0].EOS().reduce;
450 } else if (!state.compare("critical")) {
451 return components[0].crit;
452 } else if (!state.compare("triple_liquid")) {
453 return components[0].triple_liquid;
454 } else if (!state.compare("triple_vapor")) {
455 return components[0].triple_vapor;
456 } else {
457 throw ValueError(format("This state [%s] is invalid to calc_state", state.c_str()));
458 }
459 } else {
460 if (!state.compare("critical")) {
461 return _critical;
462 } else {
463 throw ValueError(format("calc_state not supported for mixtures"));
464 }
465 }
466};
469 return components[0].EOS().acentric;
470 } else {
471 throw ValueError("acentric factor cannot be calculated for mixtures");
472 }
473}
476 return components[0].gas_constant();
477 } else {
478 if (get_config_bool(NORMALIZE_GAS_CONSTANTS)) {
479 return get_config_double(R_U_CODATA);
480 } else {
481 // mass fraction weighted average of the components
482 double summer = 0;
483 for (unsigned int i = 0; i < components.size(); ++i) {
484 summer += mole_fractions[i] * components[i].gas_constant();
485 }
486 return summer;
487 }
488 }
489}
491 double summer = 0;
492 for (unsigned int i = 0; i < components.size(); ++i) {
493 summer += mole_fractions[i] * components[i].molar_mass();
494 }
495 return summer;
496}
499 if (param == iP && given == iT) {
500 // p = f(T), direct evaluation
501 switch (Q) {
502 case 0:
503 return components[0].ancillaries.pL.evaluate(value);
504 case 1:
505 return components[0].ancillaries.pV.evaluate(value);
506 }
507 } else if (param == iT && given == iP) {
508 // T = f(p), inverse evaluation
509 switch (Q) {
510 case 0:
511 return components[0].ancillaries.pL.invert(value);
512 case 1:
513 return components[0].ancillaries.pV.invert(value);
514 }
515 } else if (param == iDmolar && given == iT) {
516 // rho = f(T), inverse evaluation
517 switch (Q) {
518 case 0:
519 return components[0].ancillaries.rhoL.evaluate(value);
520 case 1:
521 return components[0].ancillaries.rhoV.evaluate(value);
522 }
523 } else if (param == iT && given == iDmolar) {
524 // T = f(rho), inverse evaluation
525 switch (Q) {
526 case 0:
527 return components[0].ancillaries.rhoL.invert(value);
528 case 1:
529 return components[0].ancillaries.rhoV.invert(value);
530 }
531 } else if (param == isurface_tension && given == iT) {
532 return components[0].ancillaries.surface_tension.evaluate(value);
533 } else {
534 throw ValueError(format("calc of %s given %s is invalid in calc_saturation_ancillary", get_parameter_information(param, "short").c_str(),
535 get_parameter_information(given, "short").c_str()));
536 }
537
538 throw ValueError(format("Q [%d] is invalid in calc_saturation_ancillary", Q));
539 } else {
540 throw NotImplementedError(format("calc_saturation_ancillary not implemented for mixtures"));
541 }
542}
543
546 return components[0].ancillaries.melting_line.evaluate(param, given, value);
547 } else {
548 throw NotImplementedError(format("calc_melting_line not implemented for mixtures"));
549 }
550}
553 if ((_phase == iphase_twophase) || (_phase == iphase_critical_point)) { // if within the two phase region or at critical point
554 return components[0].ancillaries.surface_tension.evaluate(T()); // calculate surface tension and return
555 } else { // else state point not in the two phase region
556 throw ValueError(format("surface tension is only defined within the two-phase region; Try PQ or QT inputs")); // throw error
557 }
558 } else {
559 throw NotImplementedError(format("surface tension not implemented for mixtures"));
560 }
561}
564 CoolPropDbl eta_dilute;
565 switch (components[0].transport.viscosity_dilute.type) {
568 break;
571 break;
574 break;
577 break;
580 break;
583 break;
586 break;
589 break;
590 default:
591 throw ValueError(
592 format("dilute viscosity type [%d] is invalid for fluid %s", components[0].transport.viscosity_dilute.type, name().c_str()));
593 }
594 return eta_dilute;
595 } else {
596 throw NotImplementedError(format("dilute viscosity not implemented for mixtures"));
597 }
598}
600 CoolPropDbl eta_dilute = calc_viscosity_dilute(), initial_density = 0, residual = 0;
601 return calc_viscosity_background(eta_dilute, initial_density, residual);
602}
604
605 switch (components[0].transport.viscosity_initial.type) {
608 CoolPropDbl rho = rhomolar();
609 initial_density = eta_dilute * B_eta_initial * rho; //TODO: Check units once AMTG
610 break;
611 }
614 break;
615 }
617 break;
618 }
619 }
620
621 // Higher order terms
622 switch (components[0].transport.viscosity_higher_order.type) {
625 break;
627 residual = TransportRoutines::viscosity_higher_order_friction_theory(*this);
628 break;
631 break;
634 break;
637 break;
640 break;
643 break;
646 break;
649 break;
650 default:
651 throw ValueError(
652 format("higher order viscosity type [%d] is invalid for fluid %s", components[0].transport.viscosity_dilute.type, name().c_str()));
653 }
654
655 return initial_density + residual;
656}
657
660 CoolPropDbl dilute = 0, initial_density = 0, residual = 0, critical = 0;
661 calc_viscosity_contributions(dilute, initial_density, residual, critical);
662 return dilute + initial_density + residual + critical;
663 } else {
664 set_warning_string("Mixture model for viscosity is highly approximate");
665 CoolPropDbl summer = 0;
666 for (std::size_t i = 0; i < mole_fractions.size(); ++i) {
667 shared_ptr<HelmholtzEOSBackend> HEOS(new HelmholtzEOSBackend(components[i]));
668 HEOS->update(DmolarT_INPUTS, _rhomolar, _T);
669 summer += mole_fractions[i] * log(HEOS->viscosity());
670 }
671 return exp(summer);
672 }
673}
675 CoolPropDbl& critical) {
677 // Reset the variables
678 dilute = 0;
679 initial_density = 0;
680 residual = 0;
681 critical = 0;
682
683 // Get a reference for code cleanness
684 CoolPropFluid& component = components[0];
685
686 if (!component.transport.viscosity_model_provided) {
687 throw ValueError(format("Viscosity model is not available for this fluid"));
688 }
689
690 // Check if using ECS
691 if (component.transport.viscosity_using_ECS) {
692 // Get reference fluid name
693 std::string fluid_name = component.transport.viscosity_ecs.reference_fluid;
694 std::vector<std::string> names(1, fluid_name);
695 // Get a managed pointer to the reference fluid for ECS
696 shared_ptr<HelmholtzEOSMixtureBackend> ref_fluid(new HelmholtzEOSMixtureBackend(names));
697 // Get the viscosity using ECS and stick in the critical value
698 critical = TransportRoutines::viscosity_ECS(*this, *ref_fluid);
699 return;
700 }
701
702 // Check if using Chung model
703 if (component.transport.viscosity_using_Chung) {
704 // Get the viscosity using ECS and stick in the critical value
705 critical = TransportRoutines::viscosity_Chung(*this);
706 return;
707 }
708
709 // Check if using rho*sr model
710 if (component.transport.viscosity_using_rhosr) {
711 // Get the viscosity using rho*sr model and stick in the critical value
712 critical = TransportRoutines::viscosity_rhosr(*this);
713 return;
714 }
715
717 // Evaluate hardcoded model and stick in the critical value
718 switch (component.transport.hardcoded_viscosity) {
721 break;
724 break;
727 break;
730 break;
733 break;
736 break;
739 break;
742 break;
743 default:
744 throw ValueError(
745 format("hardcoded viscosity type [%d] is invalid for fluid %s", component.transport.hardcoded_viscosity, name().c_str()));
746 }
747 return;
748 }
749
750 // -------------------------
751 // Normal evaluation
752 // -------------------------
753
754 // Dilute part
755 dilute = calc_viscosity_dilute();
756
757 // Background viscosity given by the sum of the initial density dependence and higher order terms
758 calc_viscosity_background(dilute, initial_density, residual);
759
760 // Critical part (no fluids have critical enhancement for viscosity currently)
761 critical = 0;
762 } else {
763 throw ValueError("calc_viscosity_contributions invalid for mixtures");
764 }
765}
767 CoolPropDbl& critical) {
769 // Reset the variables
770 dilute = 0;
771 initial_density = 0;
772 residual = 0;
773 critical = 0;
774
775 // Get a reference for code cleanness
776 CoolPropFluid& component = components[0];
777
778 if (!component.transport.conductivity_model_provided) {
779 throw ValueError(format("Thermal conductivity model is not available for this fluid"));
780 }
781
782 // Check if using ECS
783 if (component.transport.conductivity_using_ECS) {
784 // Get reference fluid name
785 std::string fluid_name = component.transport.conductivity_ecs.reference_fluid;
786 std::vector<std::string> name(1, fluid_name);
787 // Get a managed pointer to the reference fluid for ECS
788 shared_ptr<HelmholtzEOSMixtureBackend> ref_fluid(new HelmholtzEOSMixtureBackend(name));
789 // Get the viscosity using ECS and store in initial_density (not normally used);
790 initial_density = TransportRoutines::conductivity_ECS(*this, *ref_fluid); // Warning: not actually initial_density
791 return;
792 }
793
795 // Evaluate hardcoded model and deposit in initial_density variable
796 // Warning: not actually initial_density
797 switch (component.transport.hardcoded_conductivity) {
799 initial_density = TransportRoutines::conductivity_hardcoded_water(*this);
800 break;
802 initial_density = TransportRoutines::conductivity_hardcoded_heavywater(*this);
803 break;
805 initial_density = TransportRoutines::conductivity_hardcoded_R23(*this);
806 break;
808 initial_density = TransportRoutines::conductivity_hardcoded_helium(*this);
809 break;
811 initial_density = TransportRoutines::conductivity_hardcoded_methane(*this);
812 break;
813 default:
814 throw ValueError(format("hardcoded conductivity type [%d] is invalid for fluid %s",
815 components[0].transport.hardcoded_conductivity, name().c_str()));
816 }
817 return;
818 }
819
820 // -------------------------
821 // Normal evaluation
822 // -------------------------
823
824 // Dilute part
825 switch (component.transport.conductivity_dilute.type) {
827 dilute = TransportRoutines::conductivity_dilute_ratio_polynomials(*this);
828 break;
830 dilute = TransportRoutines::conductivity_dilute_eta0_and_poly(*this);
831 break;
833 dilute = TransportRoutines::conductivity_dilute_hardcoded_CO2(*this);
834 break;
836 dilute = TransportRoutines::conductivity_dilute_hardcoded_CO2_HuberJPCRD2016(*this);
837 break;
839 dilute = TransportRoutines::conductivity_dilute_hardcoded_ethane(*this);
840 break;
842 dilute = 0.0;
843 break;
844 default:
845 throw ValueError(
846 format("dilute conductivity type [%d] is invalid for fluid %s", components[0].transport.conductivity_dilute.type, name().c_str()));
847 }
848
849 // Residual part
850 residual = calc_conductivity_background();
851
852 // Critical part
853 switch (component.transport.conductivity_critical.type) {
855 critical = TransportRoutines::conductivity_critical_simplified_Olchowy_Sengers(*this);
856 break;
858 critical = TransportRoutines::conductivity_critical_hardcoded_R123(*this);
859 break;
861 critical = TransportRoutines::conductivity_critical_hardcoded_ammonia(*this);
862 break;
864 critical = 0.0;
865 break;
867 critical = TransportRoutines::conductivity_critical_hardcoded_CO2_ScalabrinJPCRD2006(*this);
868 break;
869 default:
870 throw ValueError(
871 format("critical conductivity type [%d] is invalid for fluid %s", components[0].transport.viscosity_dilute.type, name().c_str()));
872 }
873 } else {
874 throw ValueError("calc_conductivity_contributions invalid for mixtures");
875 }
876};
877
879 // Residual part
880 CoolPropDbl lambda_residual = _HUGE;
881 switch (components[0].transport.conductivity_residual.type) {
883 lambda_residual = TransportRoutines::conductivity_residual_polynomial(*this);
884 break;
886 lambda_residual = TransportRoutines::conductivity_residual_polynomial_and_exponential(*this);
887 break;
888 default:
889 throw ValueError(
890 format("residual conductivity type [%d] is invalid for fluid %s", components[0].transport.conductivity_residual.type, name().c_str()));
891 }
892 return lambda_residual;
893}
896 CoolPropDbl dilute = 0, initial_density = 0, residual = 0, critical = 0;
897 calc_conductivity_contributions(dilute, initial_density, residual, critical);
898 return dilute + initial_density + residual + critical;
899 } else {
900 set_warning_string("Mixture model for conductivity is highly approximate");
901 CoolPropDbl summer = 0;
902 for (std::size_t i = 0; i < mole_fractions.size(); ++i) {
903 shared_ptr<HelmholtzEOSBackend> HEOS(new HelmholtzEOSBackend(components[i]));
904 HEOS->update(DmolarT_INPUTS, _rhomolar, _T);
905 summer += mole_fractions[i] * HEOS->conductivity();
906 }
907 return summer;
908 }
909}
910void HelmholtzEOSMixtureBackend::calc_conformal_state(const std::string& reference_fluid, CoolPropDbl& T, CoolPropDbl& rhomolar) {
911 shared_ptr<CoolProp::HelmholtzEOSMixtureBackend> REF(new CoolProp::HelmholtzEOSBackend(reference_fluid));
912
913 if (T < 0 && rhomolar < 0) {
914 // Collect some parameters
915 CoolPropDbl Tc = T_critical(), Tc0 = REF->T_critical(), rhocmolar = rhomolar_critical(), rhocmolar0 = REF->rhomolar_critical();
916
917 // Starting guess values for shape factors
918 CoolPropDbl theta = 1;
919 CoolPropDbl phi = 1;
920
921 // The equivalent substance reducing ratios
922 CoolPropDbl f = Tc / Tc0 * theta;
923 CoolPropDbl h = rhocmolar0 / rhocmolar * phi; // Must be the ratio of MOLAR densities!!
924
925 // Starting guesses for conformal state
926 T = this->T() / f;
927 rhomolar = this->rhomolar() * h;
928 }
929
930 TransportRoutines::conformal_state_solver(*this, *REF, T, rhomolar);
931}
933 double summer = 0;
934 for (unsigned int i = 0; i < components.size(); ++i) {
935 summer += mole_fractions[i] * components[i].EOS().Ttriple;
936 }
937 return summer;
938}
940 double summer = 0;
941 for (unsigned int i = 0; i < components.size(); ++i) {
942 summer += mole_fractions[i] * components[i].EOS().ptriple;
943 }
944 return summer;
945}
947 if (components.size() != 1) {
948 throw ValueError(format("calc_name is only valid for pure and pseudo-pure fluids, %d components", components.size()));
949 } else {
950 return components[0].name;
951 }
952}
953
955 if ((key == iDmolar) && _rhoLmolar) return _rhoLmolar;
956 if (!SatL) throw ValueError("The saturated liquid state has not been set.");
957 return SatL->keyed_output(key);
958}
960 if ((key == iDmolar) && _rhoVmolar) return _rhoVmolar;
961 if (!SatV) throw ValueError("The saturated vapor state has not been set.");
962 return SatV->keyed_output(key);
963}
964
965void HelmholtzEOSMixtureBackend::calc_ideal_curve(const std::string& type, std::vector<double>& T, std::vector<double>& p) {
966 if (type == "Joule-Thomson") {
967 JouleThomsonCurveTracer JTCT(this, 1e5, 800);
968 JTCT.trace(T, p);
969 } else if (type == "Joule-Inversion") {
970 JouleInversionCurveTracer JICT(this, 1e5, 800);
971 JICT.trace(T, p);
972 } else if (type == "Ideal") {
973 IdealCurveTracer ICT(this, 1e5, 800);
974 ICT.trace(T, p);
975 } else if (type == "Boyle") {
976 BoyleCurveTracer BCT(this, 1e5, 800);
977 BCT.trace(T, p);
978 } else {
979 throw ValueError(format("Invalid ideal curve type: %s", type.c_str()));
980 }
981};
982std::vector<std::string> HelmholtzEOSMixtureBackend::calc_fluid_names(void) {
983 std::vector<std::string> out;
984 for (std::size_t i = 0; i < components.size(); ++i) {
985 out.push_back(components[i].name);
986 }
987 return out;
988}
990 if (components.size() != 1) {
991 throw ValueError(format("For now, calc_ODP is only valid for pure and pseudo-pure fluids, %d components", components.size()));
992 } else {
993 CoolPropDbl v = components[0].environment.ODP;
994 if (!ValidNumber(v) || v < 0) {
995 throw ValueError(format("ODP value is not specified or invalid"));
996 }
997 return v;
998 }
999}
1001 if (components.size() != 1) {
1002 throw ValueError(format("For now, calc_GWP20 is only valid for pure and pseudo-pure fluids, %d components", components.size()));
1003 } else {
1004 CoolPropDbl v = components[0].environment.GWP20;
1005 if (!ValidNumber(v) || v < 0) {
1006 throw ValueError(format("GWP20 value is not specified or invalid"));
1007 }
1008 return v;
1009 }
1010}
1012 if (components.size() != 1) {
1013 throw ValueError(format("For now, calc_GWP100 is only valid for pure and pseudo-pure fluids, %d components", components.size()));
1014 } else {
1015 CoolPropDbl v = components[0].environment.GWP100;
1016 if (!ValidNumber(v) || v < 0) {
1017 throw ValueError(format("GWP100 value is not specified or invalid"));
1018 }
1019 return v;
1020 }
1021}
1023 if (components.size() != 1) {
1024 throw ValueError(format("For now, calc_GWP500 is only valid for pure and pseudo-pure fluids, %d components", components.size()));
1025 } else {
1026 CoolPropDbl v = components[0].environment.GWP500;
1027 if (!ValidNumber(v) || v < 0) {
1028 throw ValueError(format("GWP500 value is not specified or invalid"));
1029 }
1030 return v;
1031 }
1032}
1034 if (components.size() != 1) {
1035 std::vector<CriticalState> critpts = calc_all_critical_points();
1036 if (critpts.size() == 1) {
1037 //if (!critpts[0].stable){ throw ValueError(format("found one critical point but critical point is not stable")); }
1038 return critpts[0].T;
1039 } else {
1040 throw ValueError(format("critical point finding routine found %d critical points", critpts.size()));
1041 }
1042 } else {
1043 return components[0].crit.T;
1044 }
1045}
1047 if (components.size() != 1) {
1048 std::vector<CriticalState> critpts = calc_all_critical_points();
1049 if (critpts.size() == 1) {
1050 //if (!critpts[0].stable){ throw ValueError(format("found one critical point but critical point is not stable")); }
1051 return critpts[0].p;
1052 } else {
1053 throw ValueError(format("critical point finding routine found %d critical points", critpts.size()));
1054 }
1055 } else {
1056 return components[0].crit.p;
1057 }
1058}
1060 if (components.size() != 1) {
1061 std::vector<CriticalState> critpts = calc_all_critical_points();
1062 if (critpts.size() == 1) {
1063 //if (!critpts[0].stable){ throw ValueError(format("found one critical point but critical point is not stable")); }
1064 return critpts[0].rhomolar;
1065 } else {
1066 throw ValueError(format("critical point finding routine found %d critical points", critpts.size()));
1067 }
1068 } else {
1069 return components[0].crit.rhomolar;
1070 }
1071}
1074 if (components[0].EOS().pseudo_pure) {
1075 return components[0].EOS().max_sat_p.p;
1076 } else {
1077 return p_critical();
1078 }
1079 } else {
1080 throw ValueError("calc_pmax_sat not yet defined for mixtures");
1081 }
1082}
1085 if (components[0].EOS().pseudo_pure) {
1086 double Tmax_sat = components[0].EOS().max_sat_T.T;
1087 if (!ValidNumber(Tmax_sat)) {
1088 return T_critical();
1089 } else {
1090 return Tmax_sat;
1091 }
1092 } else {
1093 return T_critical();
1094 }
1095 } else {
1096 throw ValueError("calc_Tmax_sat not yet defined for mixtures");
1097 }
1098}
1099
1102 Tmin_satL = components[0].EOS().sat_min_liquid.T;
1103 Tmin_satV = components[0].EOS().sat_min_vapor.T;
1104 return;
1105 } else {
1106 throw ValueError("calc_Tmin_sat not yet defined for mixtures");
1107 }
1108}
1109
1112 pmin_satL = components[0].EOS().sat_min_liquid.p;
1113 pmin_satV = components[0].EOS().sat_min_vapor.p;
1114 return;
1115 } else {
1116 throw ValueError("calc_pmin_sat not yet defined for mixtures");
1117 }
1118}
1119
1120// Minimum allowed saturation temperature the maximum of the saturation temperatures of liquid and vapor
1121// For pure fluids, both values are the same, for pseudo-pure they are probably the same, for mixtures they are definitely not the same
1122
1124 double summer = 0;
1125 for (unsigned int i = 0; i < components.size(); ++i) {
1126 summer += mole_fractions[i] * components[i].EOS().limits.Tmax;
1127 }
1128 return summer;
1129}
1131 double summer = 0;
1132 for (unsigned int i = 0; i < components.size(); ++i) {
1133 summer += mole_fractions[i] * components[i].EOS().limits.Tmin;
1134 }
1135 return summer;
1136}
1138 double summer = 0;
1139 for (unsigned int i = 0; i < components.size(); ++i) {
1140 summer += mole_fractions[i] * components[i].EOS().limits.pmax;
1141 }
1142 return summer;
1143}
1144
1146 // TODO: This is just a quick fix for #878 - should be done more systematically
1147 const CoolPropDbl rhomolar_min = 0;
1148 const CoolPropDbl T_min = 0;
1149
1150 if (rhomolar < rhomolar_min) {
1151 throw ValueError(format("The molar density of %f mol/m3 is below the minimum of %f mol/m3", rhomolar, rhomolar_min));
1152 }
1153
1154 if (T < T_min) {
1155 throw ValueError(format("The temperature of %f K is below the minimum of %f K", T, T_min));
1156 }
1157
1159 // Set up the state
1160 pre_update(pair, rhomolar, T);
1161
1163 _T = T;
1164 _p = calc_pressure();
1165
1166 // Cleanup
1167 bool optional_checks = false;
1168 post_update(optional_checks);
1169}
1170
1173 // Set up the state
1174 pre_update(pair, hmolar, Q);
1175
1176 _hmolar = hmolar;
1177 _Q = Q;
1178 FlashRoutines::HQ_flash(*this, Tguess);
1179
1180 // Cleanup
1181 post_update();
1182}
1184 this->_hmolar = HEOS.hmolar();
1185 this->_smolar = HEOS.smolar();
1186 this->_T = HEOS.T();
1187 this->_umolar = HEOS.umolar();
1188 this->_p = HEOS.p();
1189 this->_rhomolar = HEOS.rhomolar();
1190 this->_Q = HEOS.Q();
1191 this->_phase = HEOS.phase();
1192
1193 // Copy the derivatives as well
1194}
1197 // Set up the state
1198 pre_update(pair, p, T);
1199
1200 // Do the flash call to find rho = f(T,p)
1201 CoolPropDbl rhomolar = solver_rho_Tp(T, p, rhomolar_guess);
1202
1203 // Update the class with the new calculated density
1205
1206 // Skip the cleanup, already done in update_DmolarT_direct
1207}
1208
1210 // Clear the state
1211 clear();
1212
1213 if (is_pure_or_pseudopure == false && mole_fractions.size() == 0) {
1214 throw ValueError("Mole fractions must be set");
1215 }
1216
1217 // If the inputs are in mass units, convert them to molar units
1218 mass_to_molar_inputs(input_pair, value1, value2);
1219
1220 // Set the mole-fraction weighted gas constant for the mixture
1221 // (or the pure/pseudo-pure fluid) if it hasn't been set yet
1222 gas_constant();
1223
1224 // Calculate and cache the reducing state
1226}
1227
1228void HelmholtzEOSMixtureBackend::update(CoolProp::input_pairs input_pair, double value1, double value2) {
1229 if (get_debug_level() > 10) {
1230 std::cout << format("%s (%d): update called with (%d: (%s), %g, %g)", __FILE__, __LINE__, input_pair,
1231 get_input_pair_short_desc(input_pair).c_str(), value1, value2)
1232 << std::endl;
1233 }
1234
1235 CoolPropDbl ld_value1 = value1, ld_value2 = value2;
1236 pre_update(input_pair, ld_value1, ld_value2);
1237 value1 = ld_value1;
1238 value2 = ld_value2;
1239
1240 switch (input_pair) {
1241 case PT_INPUTS:
1242 _p = value1;
1243 _T = value2;
1245 break;
1246 case DmolarT_INPUTS:
1247 _rhomolar = value1;
1248 _T = value2;
1250 break;
1251 case SmolarT_INPUTS:
1252 _smolar = value1;
1253 _T = value2;
1255 break;
1256 //case HmolarT_INPUTS:
1257 // _hmolar = value1; _T = value2; FlashRoutines::DHSU_T_flash(*this, iHmolar); break;
1258 //case TUmolar_INPUTS:
1259 // _T = value1; _umolar = value2; FlashRoutines::DHSU_T_flash(*this, iUmolar); break;
1260 case DmolarP_INPUTS:
1261 _rhomolar = value1;
1262 _p = value2;
1264 break;
1266 _rhomolar = value1;
1267 _hmolar = value2;
1269 break;
1271 _rhomolar = value1;
1272 _smolar = value2;
1274 break;
1276 _rhomolar = value1;
1277 _umolar = value2;
1279 break;
1280 case HmolarP_INPUTS:
1281 _hmolar = value1;
1282 _p = value2;
1284 break;
1285 case PSmolar_INPUTS:
1286 _p = value1;
1287 _smolar = value2;
1289 break;
1290 case PUmolar_INPUTS:
1291 _p = value1;
1292 _umolar = value2;
1294 break;
1296 _hmolar = value1;
1297 _smolar = value2;
1299 break;
1300 case QT_INPUTS:
1301 _Q = value1;
1302 _T = value2;
1303 if ((_Q < 0) || (_Q > 1)) throw CoolProp::OutOfRangeError("Input vapor quality [Q] must be between 0 and 1");
1305 break;
1306 case PQ_INPUTS:
1307 _p = value1;
1308 _Q = value2;
1309 if ((_Q < 0) || (_Q > 1)) throw CoolProp::OutOfRangeError("Input vapor quality [Q] must be between 0 and 1");
1311 break;
1312 case QSmolar_INPUTS:
1313 _Q = value1;
1314 _smolar = value2;
1315 if ((_Q < 0) || (_Q > 1)) throw CoolProp::OutOfRangeError("Input vapor quality [Q] must be between 0 and 1");
1317 break;
1318 case HmolarQ_INPUTS:
1319 _hmolar = value1;
1320 _Q = value2;
1321 if ((_Q < 0) || (_Q > 1)) throw CoolProp::OutOfRangeError("Input vapor quality [Q] must be between 0 and 1");
1323 break;
1324 case DmolarQ_INPUTS:
1325 _rhomolar = value1;
1326 _Q = value2;
1327 if ((_Q < 0) || (_Q > 1)) throw CoolProp::OutOfRangeError("Input vapor quality [Q] must be between 0 and 1");
1329 break;
1330 default:
1331 throw ValueError(format("This pair of inputs [%s] is not yet supported", get_input_pair_short_desc(input_pair).c_str()));
1332 }
1333
1334 post_update();
1335}
1336const std::vector<CoolPropDbl> HelmholtzEOSMixtureBackend::calc_mass_fractions() {
1337 // mass fraction is mass_i/total_mass;
1338 CoolPropDbl mm = molar_mass();
1339 std::vector<CoolPropDbl>& mole_fractions = get_mole_fractions_ref();
1340 std::vector<CoolPropDbl> mass_fractions(mole_fractions.size());
1341 for (std::size_t i = 0; i < mole_fractions.size(); ++i) {
1342 double mmi = get_fluid_constant(i, imolar_mass);
1343 mass_fractions[i] = mmi * (mole_fractions[i]) / mm;
1344 }
1345 return mass_fractions;
1346}
1347
1349 const GuessesStructure& guesses) {
1350 if (get_debug_level() > 10) {
1351 std::cout << format("%s (%d): update called with (%d: (%s), %g, %g)", __FILE__, __LINE__, input_pair,
1352 get_input_pair_short_desc(input_pair).c_str(), value1, value2)
1353 << std::endl;
1354 }
1355
1356 CoolPropDbl ld_value1 = value1, ld_value2 = value2;
1357 pre_update(input_pair, ld_value1, ld_value2);
1358 value1 = ld_value1;
1359 value2 = ld_value2;
1360
1361 switch (input_pair) {
1362 case PQ_INPUTS:
1363 _p = value1;
1364 _Q = value2;
1366 break;
1367 case QT_INPUTS:
1368 _Q = value1;
1369 _T = value2;
1371 break;
1372 case PT_INPUTS:
1373 _p = value1;
1374 _T = value2;
1376 break;
1377 default:
1378 throw ValueError(format("This pair of inputs [%s] is not yet supported", get_input_pair_short_desc(input_pair).c_str()));
1379 }
1380 post_update();
1381}
1382
1384 // Check the values that must always be set
1385 //if (_p < 0){ throw ValueError("p is less than zero");}
1386 if (!ValidNumber(_p)) {
1387 throw ValueError("p is not a valid number");
1388 }
1389 //if (_T < 0){ throw ValueError("T is less than zero");}
1390 if (!ValidNumber(_T)) {
1391 throw ValueError("T is not a valid number");
1392 }
1393 if (_rhomolar < 0) {
1394 throw ValueError("rhomolar is less than zero");
1395 }
1396 if (!ValidNumber(_rhomolar)) {
1397 throw ValueError("rhomolar is not a valid number");
1398 }
1399
1400 if (optional_checks) {
1401 if (!ValidNumber(_Q)) {
1402 throw ValueError("Q is not a valid number");
1403 }
1404 if (_phase == iphase_unknown) {
1405 throw ValueError("_phase is unknown");
1406 }
1407 }
1408
1409 // Set the reduced variables
1410 _tau = _reducing.T / _T;
1412
1413 // Update the terms in the excess contribution
1414 residual_helmholtz->Excess.update(_tau, _delta);
1415}
1416
1418 return 1 / rhomolar_reducing() * calc_alphar_deriv_nocache(0, 1, mole_fractions, _tau, 1e-12);
1419}
1422 CoolPropDbl dtau_dT = -red.T / pow(_T, 2);
1423 return 1 / red.rhomolar * calc_alphar_deriv_nocache(1, 1, mole_fractions, _tau, 1e-12) * dtau_dT;
1424}
1426 return 1 / pow(rhomolar_reducing(), 2) * calc_alphar_deriv_nocache(0, 2, mole_fractions, _tau, 1e-12);
1427}
1430 CoolPropDbl dtau_dT = -red.T / pow(_T, 2);
1431 return 1 / pow(red.rhomolar, 2) * calc_alphar_deriv_nocache(1, 2, mole_fractions, _tau, 1e-12) * dtau_dT;
1432}
1434 /*
1435 Determine the phase given p and one other state variable
1436 */
1437 saturation_called = false;
1438
1439 // Reference declaration to save indexing
1440 CoolPropFluid& component = components[0];
1441
1442 // Maximum saturation temperature - Equal to critical pressure for pure fluids
1443 CoolPropDbl psat_max = calc_pmax_sat();
1444
1445 // Check supercritical pressure
1446 if (_p > psat_max) {
1447 _Q = 1e9;
1448 switch (other) {
1449 case iT: {
1450 if (_T > _crit.T) {
1452 return;
1453 } else {
1455 return;
1456 }
1457 }
1458 case iDmolar: {
1459 if (_rhomolar < _crit.rhomolar) {
1461 return;
1462 } else {
1464 return;
1465 }
1466 }
1467 case iSmolar: {
1468 if (_smolar.pt() > _crit.smolar) {
1470 return;
1471 } else {
1473 return;
1474 }
1475 }
1476 case iHmolar: {
1477 if (_hmolar.pt() > _crit.hmolar) {
1479 return;
1480 } else {
1482 return;
1483 }
1484 }
1485 case iUmolar: {
1486 if (_umolar.pt() > _crit.umolar) {
1488 return;
1489 } else {
1491 return;
1492 }
1493 }
1494 default: {
1495 throw ValueError("supercritical pressure but other invalid for now");
1496 }
1497 }
1498 }
1499 // Check between triple point pressure and psat_max
1500 else if (_p >= components[0].EOS().ptriple * 0.9999 && _p <= psat_max) {
1501 // First try the ancillaries, use them to determine the state if you can
1502
1503 // Calculate dew and bubble temps from the ancillaries (everything needs them)
1504 _TLanc = components[0].ancillaries.pL.invert(_p);
1505 _TVanc = components[0].ancillaries.pV.invert(_p);
1506
1507 bool definitely_two_phase = false;
1508
1509 // Try using the ancillaries for P,H,S if they are there
1510 switch (other) {
1511 case iT: {
1512
1513 if (has_melting_line()) {
1514 double Tm = melting_line(iT, iP, _p);
1515 if (get_config_bool(DONT_CHECK_PROPERTY_LIMITS)) {
1517 } else {
1518 if (_T < Tm - 0.001) {
1519 throw ValueError(format("For now, we don't support T [%g K] below Tmelt(p) [%g K]", _T, Tm));
1520 }
1521 }
1522 } else {
1523 if (get_config_bool(DONT_CHECK_PROPERTY_LIMITS)) {
1525 } else {
1526 if (_T < Tmin() - 0.001) {
1527 throw ValueError(format("For now, we don't support T [%g K] below Tmin(saturation) [%g K]", _T, Tmin()));
1528 }
1529 }
1530 }
1531
1532 CoolPropDbl T_vap = 0.1 + static_cast<double>(_TVanc);
1533 CoolPropDbl T_liq = -0.1 + static_cast<double>(_TLanc);
1534
1535 if (value > T_vap) {
1536 this->_phase = iphase_gas;
1537 _Q = -1000;
1538 return;
1539 } else if (value < T_liq) {
1540 this->_phase = iphase_liquid;
1541 _Q = 1000;
1542 return;
1543 }
1544 break;
1545 }
1546 case iHmolar: {
1547 if (!component.ancillaries.hL.enabled()) {
1548 break;
1549 }
1550 // Ancillaries are h-h_anchor, so add back h_anchor
1551 CoolPropDbl h_liq = component.ancillaries.hL.evaluate(_TLanc) + component.EOS().hs_anchor.hmolar;
1552 CoolPropDbl h_liq_error_band = component.ancillaries.hL.get_max_abs_error();
1553 CoolPropDbl h_vap = h_liq + component.ancillaries.hLV.evaluate(_TLanc);
1554 CoolPropDbl h_vap_error_band = h_liq_error_band + component.ancillaries.hLV.get_max_abs_error();
1555
1556 // HelmholtzEOSMixtureBackend HEOS(components);
1557 // HEOS.update(QT_INPUTS, 1, _TLanc);
1558 // double h1 = HEOS.hmolar();
1559 // HEOS.update(QT_INPUTS, 0, _TLanc);
1560 // double h0 = HEOS.hmolar();
1561
1562 // Check if in range given the accuracy of the fit
1563 if (value > h_vap + h_vap_error_band) {
1564 this->_phase = iphase_gas;
1565 _Q = -1000;
1566 return;
1567 } else if (value < h_liq - h_liq_error_band) {
1568 this->_phase = iphase_liquid;
1569 _Q = 1000;
1570 return;
1571 } else if (value > h_liq + h_liq_error_band && value < h_vap - h_vap_error_band) {
1572 definitely_two_phase = true;
1573 }
1574 break;
1575 }
1576 case iSmolar: {
1577 if (!component.ancillaries.sL.enabled()) {
1578 break;
1579 }
1580 // Ancillaries are s-s_anchor, so add back s_anchor
1581 CoolPropDbl s_anchor = component.EOS().hs_anchor.smolar;
1582 CoolPropDbl s_liq = component.ancillaries.sL.evaluate(_TLanc) + s_anchor;
1583 CoolPropDbl s_liq_error_band = component.ancillaries.sL.get_max_abs_error();
1584 CoolPropDbl s_vap = s_liq + component.ancillaries.sLV.evaluate(_TVanc);
1585 CoolPropDbl s_vap_error_band = s_liq_error_band + component.ancillaries.sLV.get_max_abs_error();
1586
1587 // Check if in range given the accuracy of the fit
1588 if (value > s_vap + s_vap_error_band) {
1589 this->_phase = iphase_gas;
1590 _Q = -1000;
1591 return;
1592 } else if (value < s_liq - s_liq_error_band) {
1593 this->_phase = iphase_liquid;
1594 _Q = 1000;
1595 return;
1596 } else if (value > s_liq + s_liq_error_band && value < s_vap - s_vap_error_band) {
1597 definitely_two_phase = true;
1598 }
1599 break;
1600 }
1601 case iUmolar: {
1602 if (!component.ancillaries.hL.enabled()) {
1603 break;
1604 }
1605 // u = h-p/rho
1606
1607 // Ancillaries are h-h_anchor, so add back h_anchor
1608 CoolPropDbl h_liq = component.ancillaries.hL.evaluate(_TLanc) + component.EOS().hs_anchor.hmolar;
1609 CoolPropDbl h_liq_error_band = component.ancillaries.hL.get_max_abs_error();
1610 CoolPropDbl h_vap = h_liq + component.ancillaries.hLV.evaluate(_TLanc);
1611 CoolPropDbl h_vap_error_band = h_liq_error_band + component.ancillaries.hLV.get_max_abs_error();
1612 CoolPropDbl rho_vap = component.ancillaries.rhoV.evaluate(_TVanc);
1613 CoolPropDbl rho_liq = component.ancillaries.rhoL.evaluate(_TLanc);
1614 CoolPropDbl u_liq = h_liq - _p / rho_liq;
1615 CoolPropDbl u_vap = h_vap - _p / rho_vap;
1616 CoolPropDbl u_liq_error_band = 1.5 * h_liq_error_band; // Most of error is in enthalpy
1617 CoolPropDbl u_vap_error_band = 1.5 * h_vap_error_band; // Most of error is in enthalpy
1618
1619 // Check if in range given the accuracy of the fit
1620 if (value > u_vap + u_vap_error_band) {
1621 this->_phase = iphase_gas;
1622 _Q = -1000;
1623 return;
1624 } else if (value < u_liq - u_liq_error_band) {
1625 this->_phase = iphase_liquid;
1626 _Q = 1000;
1627 return;
1628 } else if (value > u_liq + u_liq_error_band && value < u_vap - u_vap_error_band) {
1629 definitely_two_phase = true;
1630 }
1631 break;
1632 }
1633 default: {
1634 }
1635 }
1636
1637 // Now either density is an input, or an ancillary for h,s,u is missing
1638 // Always calculate the densities using the ancillaries
1639 if (!definitely_two_phase) {
1642 CoolPropDbl rho_vap = 0.95 * static_cast<double>(_rhoVanc);
1643 CoolPropDbl rho_liq = 1.05 * static_cast<double>(_rhoLanc);
1644 switch (other) {
1645 case iDmolar: {
1646 if (value < rho_vap) {
1647 this->_phase = iphase_gas;
1648 return;
1649 } else if (value > rho_liq) {
1650 this->_phase = iphase_liquid;
1651 return;
1652 }
1653 break;
1654 }
1655 }
1656 }
1657
1658 if (!is_pure_or_pseudopure) {
1659 throw ValueError("possibly two-phase inputs not supported for mixtures for now");
1660 }
1661
1662 // Actually have to use saturation information sadly
1663 // For the given pressure, find the saturation state
1664 // Run the saturation routines to determine the saturation densities and pressures
1666 HEOS._p = this->_p;
1667 HEOS._Q = 0; // ?? What is the best to do here? Doesn't matter for our purposes since pure fluid
1669
1670 // We called the saturation routines, so HEOS.SatL and HEOS.SatV are now updated
1671 // with the saturated liquid and vapor values, which can therefore be used in
1672 // the other solvers
1673 saturation_called = true;
1674
1675 CoolPropDbl Q;
1676
1677 if (other == iT) {
1678 if (value < HEOS.SatL->T() - 100 * DBL_EPSILON) {
1679 this->_phase = iphase_liquid;
1680 _Q = -1000;
1681 return;
1682 } else if (value > HEOS.SatV->T() + 100 * DBL_EPSILON) {
1683 this->_phase = iphase_gas;
1684 _Q = 1000;
1685 return;
1686 } else {
1687 this->_phase = iphase_twophase;
1688 }
1689 }
1690 switch (other) {
1691 case iDmolar:
1692 Q = (1 / value - 1 / HEOS.SatL->rhomolar()) / (1 / HEOS.SatV->rhomolar() - 1 / HEOS.SatL->rhomolar());
1693 break;
1694 case iSmolar:
1695 Q = (value - HEOS.SatL->smolar()) / (HEOS.SatV->smolar() - HEOS.SatL->smolar());
1696 break;
1697 case iHmolar:
1698 Q = (value - HEOS.SatL->hmolar()) / (HEOS.SatV->hmolar() - HEOS.SatL->hmolar());
1699 break;
1700 case iUmolar:
1701 Q = (value - HEOS.SatL->umolar()) / (HEOS.SatV->umolar() - HEOS.SatL->umolar());
1702 break;
1703 default:
1704 throw ValueError(format("bad input for other"));
1705 }
1706 // TODO: Check the speed penalty of these calls
1707 // Update the states
1708 if (this->SatL) this->SatL->update(DmolarT_INPUTS, HEOS.SatL->rhomolar(), HEOS.SatL->T());
1709 if (this->SatV) this->SatV->update(DmolarT_INPUTS, HEOS.SatV->rhomolar(), HEOS.SatV->T());
1710 // Update the two-Phase variables
1711 _rhoLmolar = HEOS.SatL->rhomolar();
1712 _rhoVmolar = HEOS.SatV->rhomolar();
1713
1714 //
1715 if (Q < -1e-9) {
1716 this->_phase = iphase_liquid;
1717 _Q = -1000;
1718 return;
1719 } else if (Q > 1 + 1e-9) {
1720 this->_phase = iphase_gas;
1721 _Q = 1000;
1722 return;
1723 } else {
1724 this->_phase = iphase_twophase;
1725 }
1726
1727 _Q = Q;
1728 // Load the outputs
1729 _T = _Q * HEOS.SatV->T() + (1 - _Q) * HEOS.SatL->T();
1730 _rhomolar = 1 / (_Q / HEOS.SatV->rhomolar() + (1 - _Q) / HEOS.SatL->rhomolar());
1731 return;
1732 } else if (_p < components[0].EOS().ptriple * 0.9999) {
1733 if (other == iT) {
1734 if (_T > std::max(Tmin(), Ttriple())) {
1736 } else {
1737 if (get_config_bool(DONT_CHECK_PROPERTY_LIMITS)) {
1739 } else {
1740 throw NotImplementedError(format("For now, we don't support p [%g Pa] below ptriple [%g Pa] when T [%g] is less than Tmin [%g]",
1741 _p, components[0].EOS().ptriple, _T, std::max(Tmin(), Ttriple())));
1742 }
1743 }
1744 } else {
1746 }
1747 } else {
1748 throw ValueError(format("The pressure [%g Pa] cannot be used in p_phase_determination", _p));
1749 }
1750}
1752 class Residual : public FuncWrapper1D
1753 {
1754 public:
1756 Residual(HelmholtzEOSMixtureBackend& HEOS) : HEOS(&HEOS){};
1757 double call(double T) {
1758 HEOS->update(QT_INPUTS, 1, T);
1759 // dTdp_along_sat
1760 double dTdp_along_sat =
1761 HEOS->T() * (1 / HEOS->SatV->rhomolar() - 1 / HEOS->SatL->rhomolar()) / (HEOS->SatV->hmolar() - HEOS->SatL->hmolar());
1762 // dsdT_along_sat;
1763 return HEOS->SatV->first_partial_deriv(iSmolar, iT, iP) + HEOS->SatV->first_partial_deriv(iSmolar, iP, iT) / dTdp_along_sat;
1764 }
1765 };
1767 shared_ptr<CoolProp::HelmholtzEOSMixtureBackend> HEOS_copy(new CoolProp::HelmholtzEOSMixtureBackend(get_components()));
1768 Residual resid(*HEOS_copy);
1769 const CoolProp::SimpleState& tripleV = HEOS_copy->get_components()[0].triple_vapor;
1770 double v1 = resid.call(hsat_max.T);
1771 double v2 = resid.call(tripleV.T);
1772 // If there is a sign change, there is a maxima, otherwise there is no local maxima/minima
1773 if (v1 * v2 < 0) {
1774 Brent(resid, hsat_max.T, tripleV.T, DBL_EPSILON, 1e-8, 30);
1775 ssat_max.T = resid.HEOS->T();
1776 ssat_max.p = resid.HEOS->p();
1777 ssat_max.rhomolar = resid.HEOS->rhomolar();
1778 ssat_max.hmolar = resid.HEOS->hmolar();
1779 ssat_max.smolar = resid.HEOS->smolar();
1781 } else {
1783 }
1784 }
1785}
1787 class Residualhmax : public FuncWrapper1D
1788 {
1789 public:
1791 Residualhmax(HelmholtzEOSMixtureBackend& HEOS) : HEOS(&HEOS){};
1792 double call(double T) {
1793 HEOS->update(QT_INPUTS, 1, T);
1794 // dTdp_along_sat
1795 double dTdp_along_sat =
1796 HEOS->T() * (1 / HEOS->SatV->rhomolar() - 1 / HEOS->SatL->rhomolar()) / (HEOS->SatV->hmolar() - HEOS->SatL->hmolar());
1797 // dhdT_along_sat;
1798 return HEOS->SatV->first_partial_deriv(iHmolar, iT, iP) + HEOS->SatV->first_partial_deriv(iHmolar, iP, iT) / dTdp_along_sat;
1799 }
1800 };
1801 if (!hsat_max.is_valid()) {
1802 shared_ptr<CoolProp::HelmholtzEOSMixtureBackend> HEOS_copy(new CoolProp::HelmholtzEOSMixtureBackend(get_components()));
1803 Residualhmax residhmax(*HEOS_copy);
1804 Brent(residhmax, T_critical() - 0.1, HEOS_copy->Ttriple() + 1, DBL_EPSILON, 1e-8, 30);
1805 hsat_max.T = residhmax.HEOS->T();
1806 hsat_max.p = residhmax.HEOS->p();
1807 hsat_max.rhomolar = residhmax.HEOS->rhomolar();
1808 hsat_max.hmolar = residhmax.HEOS->hmolar();
1809 hsat_max.smolar = residhmax.HEOS->smolar();
1810 }
1811}
1813 if (!ValidNumber(value)) {
1814 throw ValueError(format("value to T_phase_determination_pure_or_pseudopure is invalid"));
1815 };
1816
1817 // T is known, another input P, T, H, S, U is given (all molar)
1818 if (_T < _crit.T && _p > _crit.p) {
1819 // Only ever true if (other = iP); otherwise _p = -HUGE
1821 } else if (std::abs(_T - _crit.T) < 10 * DBL_EPSILON) // Exactly at Tcrit
1822 {
1823 switch (other) {
1824 case iDmolar:
1825 if (std::abs(_rhomolar - _crit.rhomolar) < 10 * DBL_EPSILON) {
1827 break;
1828 } else if (_rhomolar > _crit.rhomolar) {
1830 break;
1831 } else {
1833 break;
1834 }
1835 case iP: {
1836 if (std::abs(_p - _crit.p) < 10 * DBL_EPSILON) {
1838 break;
1839 } else if (_p > _crit.p) {
1841 break;
1842 } else {
1844 break;
1845 }
1846 }
1847 default:
1848 throw ValueError(format("T=Tcrit; invalid input for other to T_phase_determination_pure_or_pseudopure"));
1849 }
1850 } else if (_T < _crit.T) // Gas, 2-Phase, Liquid, or Supercritical Liquid Region
1851 {
1852 // Start to think about the saturation stuff
1853 // First try to use the ancillary equations if you are far enough away
1854 // You know how accurate the ancillary equations are thanks to using CoolProp code to refit them
1855 switch (other) {
1856 case iP: {
1857 _pLanc = components[0].ancillaries.pL.evaluate(_T);
1858 _pVanc = components[0].ancillaries.pV.evaluate(_T);
1859 CoolPropDbl p_vap = 0.98 * static_cast<double>(_pVanc);
1860 CoolPropDbl p_liq = 1.02 * static_cast<double>(_pLanc);
1861
1862 if (value < p_vap) {
1863 this->_phase = iphase_gas;
1864 _Q = -1000;
1865 return;
1866 } else if (value > p_liq) {
1867 this->_phase = iphase_liquid;
1868 _Q = 1000;
1869 return;
1870 } else if (!is_pure()) // pseudo-pure
1871 {
1872 // For pseudo-pure fluids, the ancillary pressure curves are the official
1873 // arbiter of the phase
1874 if (value > static_cast<CoolPropDbl>(_pLanc)) {
1875 this->_phase = iphase_liquid;
1876 _Q = 1000;
1877 return;
1878 } else if (value < static_cast<CoolPropDbl>(_pVanc)) {
1879 this->_phase = iphase_gas;
1880 _Q = -1000;
1881 return;
1882 } else {
1883 throw ValueError("Two-phase inputs not supported for pseudo-pure for now");
1884 }
1885 }
1886 break;
1887 }
1888 default: {
1889 // Always calculate the densities using the ancillaries
1890 _rhoVanc = components[0].ancillaries.rhoV.evaluate(_T);
1891 _rhoLanc = components[0].ancillaries.rhoL.evaluate(_T);
1892 CoolPropDbl rho_vap = 0.95 * static_cast<double>(_rhoVanc);
1893 CoolPropDbl rho_liq = 1.05 * static_cast<double>(_rhoLanc);
1894 switch (other) {
1895 case iDmolar: {
1896 if (value < rho_vap) {
1897 this->_phase = iphase_gas;
1898 return;
1899 } else if (value > rho_liq) {
1900 this->_phase = iphase_liquid;
1901 return;
1902 } else {
1903 // Next we check the vapor quality based on the ancillary values
1904 double Qanc = (1 / value - 1 / static_cast<double>(_rhoLanc))
1905 / (1 / static_cast<double>(_rhoVanc) - 1 / static_cast<double>(_rhoLanc));
1906 // If the vapor quality is significantly inside the two-phase zone, stop, we are definitely two-phase
1907 if (value > 0.95 * rho_liq || value < 1.05 * rho_vap) {
1908 // Definitely single-phase
1909 _phase = iphase_liquid; // Needed for direct update call
1910 _Q = -1000; // Needed for direct update call
1911 update_DmolarT_direct(value, _T);
1912 CoolPropDbl pL = components[0].ancillaries.pL.evaluate(_T);
1913 if (Qanc < 0.01 && _p > pL * 1.05 && first_partial_deriv(iP, iDmolar, iT) > 0
1916 _Q = -1000;
1917 return;
1918 } else if (Qanc > 1.01) {
1919 break;
1920 } else {
1922 _p = _HUGE;
1923 }
1924 }
1925 }
1926 break;
1927 }
1928 default: {
1929 if (!this->SatL || !this->SatV) {
1930 throw ValueError(format("The saturation properties are needed in T_phase_determination_pure_or_pseudopure"));
1931 }
1932 // If it is not density, update the states
1933 SatV->update(DmolarT_INPUTS, rho_vap, _T);
1934 SatL->update(DmolarT_INPUTS, rho_liq, _T);
1935
1936 // First we check ancillaries
1937 switch (other) {
1938 case iSmolar: {
1939 if (value > SatV->calc_smolar()) {
1940 this->_phase = iphase_gas;
1941 return;
1942 }
1943 if (value < SatL->calc_smolar()) {
1944 this->_phase = iphase_liquid;
1945 return;
1946 }
1947 break;
1948 }
1949 case iHmolar: {
1950 if (value > SatV->calc_hmolar()) {
1951 this->_phase = iphase_gas;
1952 return;
1953 } else if (value < SatL->calc_hmolar()) {
1954 this->_phase = iphase_liquid;
1955 return;
1956 }
1957 break;
1958 }
1959 case iUmolar: {
1960 if (value > SatV->calc_umolar()) {
1961 this->_phase = iphase_gas;
1962 return;
1963 } else if (value < SatL->calc_umolar()) {
1964 this->_phase = iphase_liquid;
1965 return;
1966 }
1967 break;
1968 }
1969 default:
1970 throw ValueError(format("invalid input for other to T_phase_determination_pure_or_pseudopure"));
1971 }
1972 }
1973 }
1974 }
1975 }
1976
1977 // Actually have to use saturation information sadly
1978 // For the given temperature, find the saturation state
1979 // Run the saturation routines to determine the saturation densities and pressures
1983
1984 CoolPropDbl Q;
1985
1986 if (other == iP) {
1987 if (value > HEOS.SatL->p() * (1e-6 + 1)) {
1988 this->_phase = iphase_liquid;
1989 _Q = -1000;
1990 return;
1991 } else if (value < HEOS.SatV->p() * (1 - 1e-6)) {
1992 this->_phase = iphase_gas;
1993 _Q = 1000;
1994 return;
1995 } else {
1996 throw ValueError(
1997 format("Saturation pressure [%g Pa] corresponding to T [%g K] is within 1e-4 %% of given p [%Lg Pa]", HEOS.SatL->p(), _T, value));
1998 }
1999 }
2000
2001 switch (other) {
2002 case iDmolar:
2003 Q = (1 / value - 1 / HEOS.SatL->rhomolar()) / (1 / HEOS.SatV->rhomolar() - 1 / HEOS.SatL->rhomolar());
2004 break;
2005 case iSmolar:
2006 Q = (value - HEOS.SatL->smolar()) / (HEOS.SatV->smolar() - HEOS.SatL->smolar());
2007 break;
2008 case iHmolar:
2009 Q = (value - HEOS.SatL->hmolar()) / (HEOS.SatV->hmolar() - HEOS.SatL->hmolar());
2010 break;
2011 case iUmolar:
2012 Q = (value - HEOS.SatL->umolar()) / (HEOS.SatV->umolar() - HEOS.SatL->umolar());
2013 break;
2014 default:
2015 throw ValueError(format("bad input for other"));
2016 }
2017
2018 // Update the states
2019 if (this->SatL) this->SatL->update(DmolarT_INPUTS, HEOS.SatL->rhomolar(), HEOS.SatL->T());
2020 if (this->SatV) this->SatV->update(DmolarT_INPUTS, HEOS.SatV->rhomolar(), HEOS.SatV->T());
2021 // Update the two-Phase variables
2022 _rhoLmolar = HEOS.SatL->rhomolar();
2023 _rhoVmolar = HEOS.SatV->rhomolar();
2024
2025 if (Q < 0) {
2026 this->_phase = iphase_liquid;
2027 _Q = -1;
2028 return;
2029 } else if (Q > 1) {
2030 this->_phase = iphase_gas;
2031 _Q = 1;
2032 return;
2033 } else {
2034 this->_phase = iphase_twophase;
2035 }
2036 _Q = Q;
2037 // Load the outputs
2038 _p = _Q * HEOS.SatV->p() + (1 - _Q) * HEOS.SatL->p();
2039 _rhomolar = 1 / (_Q / HEOS.SatV->rhomolar() + (1 - _Q) / HEOS.SatL->rhomolar());
2040 return;
2041 } else if (_T > _crit.T && _T > components[0].EOS().Ttriple) // Supercritical or Supercritical Gas Region
2042 {
2043 _Q = 1e9;
2044 switch (other) {
2045 case iP: {
2046 if (_p > _crit.p) {
2048 return;
2049 } else {
2051 return;
2052 }
2053 }
2054 case iDmolar: {
2055 if (_rhomolar > _crit.rhomolar) {
2057 return;
2058 } else {
2060 return;
2061 }
2062 }
2063 case iSmolar: {
2064 if (_smolar.pt() > _crit.smolar) {
2066 return;
2067 } else {
2069 return;
2070 }
2071 }
2072 case iHmolar: {
2073 if (_hmolar.pt() > _crit.hmolar) {
2075 return;
2076 } else {
2078 return;
2079 }
2080 }
2081 case iUmolar: {
2082 if (_umolar.pt() > _crit.umolar) {
2084 return;
2085 } else {
2087 return;
2088 }
2089 }
2090 default: {
2091 throw ValueError("supercritical temp but other invalid for now");
2092 }
2093 }
2094 } else {
2095 throw ValueError(format("For now, we don't support T [%g K] below Ttriple [%g K]", _T, components[0].EOS().Ttriple));
2096 }
2097}
2099 CoolPropDbl T = HEOS->T(), rho = HEOS->rhomolar(), rhor = HEOS->get_reducing_state().rhomolar, Tr = HEOS->get_reducing_state().T,
2100 dT_dtau = -pow(T, 2) / Tr, R = HEOS->gas_constant(), delta = rho / rhor, tau = Tr / T;
2101
2102 switch (index) {
2103 case iT:
2104 dT = 1;
2105 drho = 0;
2106 break;
2107 case iDmolar:
2108 dT = 0;
2109 drho = 1;
2110 break;
2111 case iDmass:
2112 dT = 0;
2113 drho = HEOS->molar_mass();
2114 break;
2115 case iP: {
2116 // dp/drho|T
2117 drho = R * T * (1 + 2 * delta * HEOS->dalphar_dDelta() + pow(delta, 2) * HEOS->d2alphar_dDelta2());
2118 // dp/dT|rho
2119 dT = rho * R * (1 + delta * HEOS->dalphar_dDelta() - tau * delta * HEOS->d2alphar_dDelta_dTau());
2120 break;
2121 }
2122 case iHmass:
2123 case iHmolar: {
2124 // dh/dT|rho
2125 dT = R
2126 * (-pow(tau, 2) * (HEOS->d2alpha0_dTau2() + HEOS->d2alphar_dTau2())
2127 + (1 + delta * HEOS->dalphar_dDelta() - tau * delta * HEOS->d2alphar_dDelta_dTau()));
2128 // dh/drhomolar|T
2129 drho =
2130 T * R / rho * (tau * delta * HEOS->d2alphar_dDelta_dTau() + delta * HEOS->dalphar_dDelta() + pow(delta, 2) * HEOS->d2alphar_dDelta2());
2131 if (index == iHmass) {
2132 // dhmolar/drhomolar|T * dhmass/dhmolar where dhmass/dhmolar = 1/mole mass
2133 drho /= HEOS->molar_mass();
2134 dT /= HEOS->molar_mass();
2135 }
2136 break;
2137 }
2138 case iSmass:
2139 case iSmolar: {
2140 // ds/dT|rho
2141 dT = R / T * (-pow(tau, 2) * (HEOS->d2alpha0_dTau2() + HEOS->d2alphar_dTau2()));
2142 // ds/drho|T
2143 drho = R / rho * (-(1 + delta * HEOS->dalphar_dDelta() - tau * delta * HEOS->d2alphar_dDelta_dTau()));
2144 if (index == iSmass) {
2145 // ds/drho|T / drhomass/drhomolar where drhomass/drhomolar = mole mass
2146 drho /= HEOS->molar_mass();
2147 dT /= HEOS->molar_mass();
2148 }
2149 break;
2150 }
2151 case iUmass:
2152 case iUmolar: {
2153 // du/dT|rho
2154 dT = R * (-pow(tau, 2) * (HEOS->d2alpha0_dTau2() + HEOS->d2alphar_dTau2()));
2155 // du/drho|T
2156 drho = HEOS->T() * R / rho * (tau * delta * HEOS->d2alphar_dDelta_dTau());
2157 if (index == iUmass) {
2158 // du/drho|T / drhomass/drhomolar where drhomass/drhomolar = mole mass
2159 drho /= HEOS->molar_mass();
2160 dT /= HEOS->molar_mass();
2161 }
2162 break;
2163 }
2164 case iTau:
2165 dT = 1 / dT_dtau;
2166 drho = 0;
2167 break;
2168 case iDelta:
2169 dT = 0;
2170 drho = 1 / rhor;
2171 break;
2172 default:
2173 throw ValueError(format("input to get_dT_drho[%s] is invalid", get_parameter_information(index, "short").c_str()));
2174 }
2175}
2176
2179 CoolPropDbl delta = rhomolar / reducing.rhomolar;
2180 CoolPropDbl tau = reducing.T / T;
2181
2182 // Calculate derivative
2183 int nTau = 0, nDelta = 1;
2185
2186 // Get pressure
2187 return rhomolar * gas_constant() * T * (1 + delta * dalphar_dDelta);
2188}
2190 CoolPropDbl& light, CoolPropDbl& heavy) {
2191
2193 class dpdrho_resid : public FuncWrapper1DWithTwoDerivs
2194 {
2195 public:
2197 CoolPropDbl T, p, delta, rhor, tau, R_u;
2198
2200 : HEOS(HEOS),
2201 T(T),
2202 p(p),
2203 delta(_HUGE),
2204 rhor(HEOS->get_reducing_state().rhomolar),
2205 tau(HEOS->get_reducing_state().T / T),
2206 R_u(HEOS->gas_constant()) {}
2207 double call(double rhomolar) {
2208 delta = rhomolar / rhor; // needed for derivative
2210 // dp/drho|T
2211 return R_u * T * (1 + 2 * delta * HEOS->dalphar_dDelta() + POW2(delta) * HEOS->d2alphar_dDelta2());
2212 };
2213 double deriv(double rhomolar) {
2214 // d2p/drho2|T
2215 return R_u * T / rhor * (2 * HEOS->dalphar_dDelta() + 4 * delta * HEOS->d2alphar_dDelta2() + POW2(delta) * HEOS->calc_d3alphar_dDelta3());
2216 };
2217 double second_deriv(double rhomolar) {
2218 // d3p/drho3|T
2219 return R_u * T / POW2(rhor)
2220 * (6 * HEOS->d2alphar_dDelta2() + 6 * delta * HEOS->d3alphar_dDelta3() + POW2(delta) * HEOS->calc_d4alphar_dDelta4());
2221 };
2222 };
2223 dpdrho_resid resid(this, T, p);
2224 light = -1;
2225 heavy = -1;
2226 try {
2227 light = Halley(resid, 1e-6, 1e-8, 100);
2228 double d2pdrho2__constT = resid.deriv(light);
2229 if (d2pdrho2__constT > 0) {
2230 // Not possible since curvature should be negative
2231 throw CoolProp::ValueError("curvature cannot be positive");
2232 }
2233 } catch (std::exception& e) {
2234 if (get_debug_level() > 5) {
2235 std::cout << e.what() << std::endl;
2236 };
2237 light = -1;
2238 }
2239
2240 if (light < 0) {
2241 try {
2242 // Now we are going to do something VERY slow - increase density until curvature is positive
2243 double rho = 1e-6;
2244 for (std::size_t counter = 0; counter <= 100; counter++) {
2245 resid.call(rho); // Updates the state
2246 double curvature = resid.deriv(rho);
2247 if (curvature > 0) {
2248 light = rho;
2249 break;
2250 }
2251 rho *= 2;
2252 }
2253 } catch (...) {
2254 }
2255 }
2256
2257 // First try a "normal" calculation of the stationary point on the liquid side
2258 for (double omega = 0.7; omega > 0; omega -= 0.2) {
2259 try {
2260 resid.options.add_number("omega", omega);
2261 heavy = Halley(resid, rhomax, 1e-8, 100);
2262 double d2pdrho2__constT = resid.deriv(heavy);
2263 if (d2pdrho2__constT < 0) {
2264 // Not possible since curvature should be positive
2265 throw CoolProp::ValueError("curvature cannot be negative");
2266 }
2267 break; // Jump out, we got a good solution
2268 } catch (std::exception& e) {
2269 if (get_debug_level() > 5) {
2270 std::cout << e.what() << std::endl;
2271 };
2272 heavy = -1;
2273 }
2274 }
2275
2276 if (heavy < 0) {
2277 try {
2278 // Now we are going to do something VERY slow - decrease density until curvature is negative or pressure is negative
2279 double rho = rhomax;
2280 for (std::size_t counter = 0; counter <= 100; counter++) {
2281 resid.call(rho); // Updates the state
2282 double curvature = resid.deriv(rho);
2283 if (curvature < 0 || this->p() < 0) {
2284 heavy = rho;
2285 break;
2286 }
2287 rho /= 1.1;
2288 }
2289 } catch (...) {
2290 }
2291 }
2292
2293 if (light > 0 && heavy > 0) {
2294 // Found two stationary points, done!
2296 }
2297 // If no solution is found for dpdrho|T=0 starting at high and low densities,
2298 // then try to do a bounded solver to see if you can find any solutions. If you
2299 // can't, p = f(rho) is probably monotonic (supercritical?), and the bounds are
2300 else if (light < 0 && heavy < 0) {
2301 double dpdrho_min = resid.call(1e-10);
2302 double dpdrho_max = resid.call(rhomax);
2303 if (dpdrho_max * dpdrho_min > 0) {
2305 } else {
2306 throw CoolProp::ValueError("zero stationary points -- does this make sense?");
2307 }
2308 } else {
2310 }
2311}
2312// Define the residual to be driven to zero
2314{
2315 public:
2318
2320 : HEOS(HEOS),
2321 T(T),
2322 p(p),
2323 delta(_HUGE),
2324 rhor(HEOS->get_reducing_state().rhomolar),
2325 tau(HEOS->get_reducing_state().T / T),
2326 R_u(HEOS->gas_constant()) {}
2327 double call(double rhomolar) {
2328 delta = rhomolar / rhor; // needed for derivative
2329 HEOS->update_DmolarT_direct(rhomolar, T);
2330 CoolPropDbl peos = HEOS->p();
2331 return (peos - p) / p;
2332 };
2333 double deriv(double rhomolar) {
2334 // dp/drho|T / pspecified
2335 return R_u * T * (1 + 2 * delta * HEOS->dalphar_dDelta() + POW2(delta) * HEOS->d2alphar_dDelta2()) / p;
2336 };
2337 double second_deriv(double rhomolar) {
2338 // d2p/drho2|T / pspecified
2339 return R_u * T / rhor * (2 * HEOS->dalphar_dDelta() + 4 * delta * HEOS->d2alphar_dDelta2() + POW2(delta) * HEOS->calc_d3alphar_dDelta3()) / p;
2340 };
2341 double third_deriv(double rhomolar) {
2342 // d3p/drho3|T / pspecified
2343 return R_u * T / POW2(rhor)
2345 };
2346};
2348 double b = 0;
2349 for (std::size_t i = 0; i < mole_fractions.size(); ++i) {
2350 // Get the parameters for the cubic EOS
2352 CoolPropDbl R = 8.3144598;
2353 b += mole_fractions[i] * 0.08664 * R * Tc / pc;
2354 }
2355 return b;
2356}
2358 // Find the densities along the isotherm where dpdrho|T = 0 (if you can)
2359 CoolPropDbl light = -1, heavy = -1;
2360 StationaryPointReturnFlag retval = solver_dpdrho0_Tp(T, p, rhomolar_max, light, heavy);
2361
2362 // Define the solver class
2363 SolverTPResid resid(this, T, p);
2364
2365 if (retval == ZERO_STATIONARY_POINTS) {
2366 // It's monotonic (no stationary points found), so do the full bounded solver
2367 // for the density
2368 double rho = Brent(resid, 1e-10, rhomolar_max, DBL_EPSILON, 1e-8, 100);
2369 return rho;
2370 } else if (retval == TWO_STATIONARY_POINTS_FOUND) {
2371
2372 // Calculate the pressures at the min and max densities where dpdrho|T = 0
2373 double p_at_rhomin_stationary = calc_pressure_nocache(T, light);
2374 double p_at_rhomax_stationary = calc_pressure_nocache(T, heavy);
2375
2376 double rho_liq = -1, rho_vap = -1;
2377 if (p > p_at_rhomax_stationary) {
2378 int counter = 0;
2379 for (/* init above, for debugging */; counter <= 10; counter++) {
2380 // Bump up rhomax if needed to bound the given pressure
2381 double p_at_rhomax = calc_pressure_nocache(T, rhomolar_max);
2382 if (p_at_rhomax < p) {
2383 rhomolar_max *= 1.05;
2384 } else {
2385 break;
2386 }
2387 }
2388 // Look for liquid root starting at stationary point density
2389 rho_liq = Brent(resid, heavy, rhomolar_max, DBL_EPSILON, 1e-8, 100);
2390 }
2391
2392 if (p < p_at_rhomin_stationary) {
2393 // Look for vapor root starting at stationary point density
2394 rho_vap = Brent(resid, light, 1e-10, DBL_EPSILON, 1e-8, 100);
2395 }
2396
2397 if (rho_vap > 0 && rho_liq > 0) {
2398 // Both densities are the same
2399 if (std::abs(rho_vap - rho_liq) < 1e-10) {
2400 // return one of them
2401 return rho_vap;
2402 } else {
2403 // Two solutions found, keep the one with lower Gibbs energy
2404 double gibbsmolar_vap = calc_gibbsmolar_nocache(T, rho_vap);
2405 double gibbsmolar_liq = calc_gibbsmolar_nocache(T, rho_liq);
2406 if (gibbsmolar_liq < gibbsmolar_vap) {
2407 return rho_liq;
2408 } else {
2409 return rho_vap;
2410 }
2411 }
2412 } else if (rho_vap < 0 && rho_liq > 0) {
2413 // Liquid root found, return it
2414 return rho_liq;
2415 } else if (rho_vap > 0 && rho_liq < 0) {
2416 // Vapor root found, return it
2417 return rho_vap;
2418 } else {
2419 throw CoolProp::ValueError(format("No density solutions for T=%g,p=%g,z=%s", T, p, vec_to_string(static_cast<std::vector<double>>(mole_fractions), "%0.12g").c_str()));
2420 }
2421 } else {
2423 format("One stationary point (not good) for T=%g,p=%g,z=%s", T, p, vec_to_string(static_cast<std::vector<double>>(mole_fractions), "%0.12g").c_str()));
2424 }
2425};
2426
2428 phases phase;
2429
2430 SolverTPResid resid(this, T, p);
2431
2432 // Check if the phase is imposed
2434 // Use the imposed phase index
2436 else
2437 // Use the phase index in the class
2438 phase = _phase;
2439
2440 if (rhomolar_guess < 0) // Not provided
2441 {
2442 // Calculate a guess value using SRK equation of state
2443 rhomolar_guess = solver_rho_Tp_SRK(T, p, phase);
2444
2445 // A gas-like phase, ideal gas might not be the perfect model, but probably good enough
2447 if (rhomolar_guess < 0 || !ValidNumber(rhomolar_guess)) // If the guess is bad, probably high temperature, use ideal gas
2448 {
2449 rhomolar_guess = p / (gas_constant() * T);
2450 }
2451 } else if (phase == iphase_liquid) {
2452 double rhomolar;
2454 // It's liquid at subcritical pressure, we can use ancillaries as guess value
2455 CoolPropDbl _rhoLancval = static_cast<CoolPropDbl>(components[0].ancillaries.rhoL.evaluate(T));
2456 try {
2457 // First we try with Halley's method starting at saturated liquid
2458 rhomolar = Halley(resid, _rhoLancval, 1e-8, 100);
2461 throw ValueError("Liquid density is invalid");
2462 }
2463 } catch (std::exception&) {
2464 // Next we try with a Brent method bounded solver since the function is 1-1
2465 rhomolar = Brent(resid, _rhoLancval * 0.9, _rhoLancval * 1.3, DBL_EPSILON, 1e-8, 100);
2466 if (!ValidNumber(rhomolar)) {
2467 throw ValueError();
2468 }
2469 }
2470 } else {
2471 // Try with 4th order Householder method starting at a very high density
2472 rhomolar = Householder4(&resid, 3 * rhomolar_reducing(), 1e-8, 100);
2473 }
2474 return rhomolar;
2475 } else if (phase == iphase_supercritical_liquid) {
2476 CoolPropDbl rhoLancval = static_cast<CoolPropDbl>(components[0].ancillaries.rhoL.evaluate(T));
2477 // Next we try with a Brent method bounded solver since the function is 1-1
2478 double rhomolar = Brent(resid, rhoLancval * 0.99, rhomolar_critical() * 4, DBL_EPSILON, 1e-8, 100);
2479 if (!ValidNumber(rhomolar)) {
2480 throw ValueError();
2481 }
2482 return rhomolar;
2483 }
2484 }
2485
2486 try {
2487 double rhomolar = Householder4(resid, rhomolar_guess, 1e-8, 20);
2488 if (!ValidNumber(rhomolar) || rhomolar < 0) {
2489 throw ValueError();
2490 }
2491 if (phase == iphase_liquid) {
2492 double dpdrho = first_partial_deriv(iP, iDmolar, iT);
2493 double d2pdrho2 = second_partial_deriv(iP, iDmolar, iT, iDmolar, iT);
2494 if (dpdrho < 0 || d2pdrho2 < 0) {
2495 // Try again with a larger density in order to end up at the right solution
2496 rhomolar = Householder4(resid, 3 * rhomolar_reducing(), 1e-8, 100);
2497 return rhomolar;
2498 }
2499 } else if (phase == iphase_gas) {
2500 double dpdrho = first_partial_deriv(iP, iDmolar, iT);
2501 double d2pdrho2 = second_partial_deriv(iP, iDmolar, iT, iDmolar, iT);
2502 if (dpdrho < 0 || d2pdrho2 > 0) {
2503 // Try again with a tiny density in order to end up at the right solution
2504 rhomolar = Householder4(resid, 1e-6, 1e-8, 100);
2505 return rhomolar;
2506 }
2507 }
2508 return rhomolar;
2509 } catch (std::exception& e) {
2511 double rhomolar = Brent(resid, 1e-10, 3 * rhomolar_reducing(), DBL_EPSILON, 1e-8, 100);
2512 return rhomolar;
2513 } else if (is_pure_or_pseudopure && T > T_critical()) {
2514 try {
2515 double rhomolar = Brent(resid, 1e-10, 5 * rhomolar_reducing(), DBL_EPSILON, 1e-8, 100);
2516 return rhomolar;
2517
2518 } catch (...) {
2519 double rhomolar = Householder4(resid, 3 * rhomolar_reducing(), 1e-8, 100);
2520 return rhomolar;
2521 }
2522 }
2523 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,
2524 rhomolar_guess, e.what()));
2525 }
2526}
2528 CoolPropDbl rhomolar, R_u = gas_constant(), a = 0, b = 0, k_ij = 0;
2529
2530 for (std::size_t i = 0; i < components.size(); ++i) {
2531 CoolPropDbl Tci = components[i].EOS().reduce.T, pci = components[i].EOS().reduce.p, acentric_i = components[i].EOS().acentric;
2532 CoolPropDbl m_i = 0.480 + 1.574 * acentric_i - 0.176 * pow(acentric_i, 2);
2533 CoolPropDbl b_i = 0.08664 * R_u * Tci / pci;
2534 b += mole_fractions[i] * b_i;
2535
2536 CoolPropDbl a_i = 0.42747 * pow(R_u * Tci, 2) / pci * pow(1 + m_i * (1 - sqrt(T / Tci)), 2);
2537
2538 for (std::size_t j = 0; j < components.size(); ++j) {
2539 CoolPropDbl Tcj = components[j].EOS().reduce.T, pcj = components[j].EOS().reduce.p, acentric_j = components[j].EOS().acentric;
2540 CoolPropDbl m_j = 0.480 + 1.574 * acentric_j - 0.176 * pow(acentric_j, 2);
2541
2542 CoolPropDbl a_j = 0.42747 * pow(R_u * Tcj, 2) / pcj * pow(1 + m_j * (1 - sqrt(T / Tcj)), 2);
2543
2544 k_ij = 0;
2545 //if (i == j){
2546 // k_ij = 0;
2547 //}
2548 //else{
2549 // k_ij = 0;
2550 //}
2551
2552 a += mole_fractions[i] * mole_fractions[j] * sqrt(a_i * a_j) * (1 - k_ij);
2553 }
2554 }
2555
2556 CoolPropDbl A = a * p / pow(R_u * T, 2);
2557 CoolPropDbl B = b * p / (R_u * T);
2558
2559 //Solve the cubic for solutions for Z = p/(rho*R*T)
2560 double Z0, Z1, Z2;
2561 int Nsolns;
2562 solve_cubic(1, -1, A - B - B * B, -A * B, Nsolns, Z0, Z1, Z2);
2563
2564 // Determine the guess value
2565 if (Nsolns == 1) {
2566 rhomolar = p / (Z0 * R_u * T);
2567 } else {
2568 CoolPropDbl rhomolar0 = p / (Z0 * R_u * T);
2569 CoolPropDbl rhomolar1 = p / (Z1 * R_u * T);
2570 CoolPropDbl rhomolar2 = p / (Z2 * R_u * T);
2571
2572 // Check if only one solution is positive, return the solution if that is the case
2573 if (rhomolar0 > 0 && rhomolar1 <= 0 && rhomolar2 <= 0) {
2574 return rhomolar0;
2575 }
2576 if (rhomolar0 <= 0 && rhomolar1 > 0 && rhomolar2 <= 0) {
2577 return rhomolar1;
2578 }
2579 if (rhomolar0 <= 0 && rhomolar1 <= 0 && rhomolar2 > 0) {
2580 return rhomolar2;
2581 }
2582
2583 switch (phase) {
2584 case iphase_liquid:
2586 rhomolar = max3(rhomolar0, rhomolar1, rhomolar2);
2587 break;
2588 case iphase_gas:
2591 rhomolar = min3(rhomolar0, rhomolar1, rhomolar2);
2592 break;
2593 default:
2594 throw ValueError("Bad phase to solver_rho_Tp_SRK");
2595 };
2596 }
2597 return rhomolar;
2598}
2599
2601 // Calculate the reducing parameters
2603 _tau = _reducing.T / _T;
2604
2605 // Calculate derivative if needed
2606 CoolPropDbl dar_dDelta = dalphar_dDelta();
2607 CoolPropDbl R_u = gas_constant();
2608
2609 // Get pressure
2610 _p = _rhomolar * R_u * _T * (1 + _delta.pt() * dar_dDelta);
2611
2612 //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);
2613 //if (_p < 0){
2614 // throw ValueError("Pressure is less than zero");
2615 //}
2616
2617 return static_cast<CoolPropDbl>(_p);
2618}
2620 // Calculate the reducing parameters
2623
2624 // Calculate derivatives if needed, or just use cached values
2625 // Calculate derivative if needed
2629 CoolPropDbl R_u = gas_constant();
2630
2631 // Get molar enthalpy
2632 return R_u * T * (1 + tau * (da0_dTau + dar_dTau) + delta * dar_dDelta);
2633}
2635 if (get_debug_level() >= 50)
2636 std::cout << format("HelmholtzEOSMixtureBackend::calc_hmolar: 2phase: %d T: %g rhomomolar: %g", isTwoPhase(), _T, _rhomolar) << std::endl;
2637 if (isTwoPhase()) {
2638 if (!this->SatL || !this->SatV) throw ValueError(format("The saturation properties are needed for the two-phase properties"));
2639 if (std::abs(_Q) < DBL_EPSILON) {
2640 _hmolar = SatL->hmolar();
2641 } else if (std::abs(_Q - 1) < DBL_EPSILON) {
2642 _hmolar = SatV->hmolar();
2643 } else {
2644 _hmolar = _Q * SatV->hmolar() + (1 - _Q) * SatL->hmolar();
2645 }
2646 return static_cast<CoolPropDbl>(_hmolar);
2647 } else if (isHomogeneousPhase()) {
2648 // Calculate the reducing parameters
2650 _tau = _reducing.T / _T;
2651
2652 // Calculate derivatives if needed, or just use cached values
2653 CoolPropDbl da0_dTau = dalpha0_dTau();
2654 CoolPropDbl dar_dTau = dalphar_dTau();
2655 CoolPropDbl dar_dDelta = dalphar_dDelta();
2656 CoolPropDbl R_u = gas_constant();
2657
2658 // Get molar enthalpy
2659 _hmolar = R_u * _T * (1 + _tau.pt() * (da0_dTau + dar_dTau) + _delta.pt() * dar_dDelta);
2660
2661 return static_cast<CoolPropDbl>(_hmolar);
2662 } else {
2663 throw ValueError(format("phase is invalid in calc_hmolar"));
2664 }
2665}
2667 // Calculate the reducing parameters
2670
2671 // Calculate derivatives if needed, or just use cached values
2672 // Calculate derivative if needed
2677 CoolPropDbl R_u = gas_constant();
2678
2679 // Get molar entropy
2680 return R_u * (tau * (da0_dTau + dar_dTau) - a0 - ar);
2681}
2683 if (isTwoPhase()) {
2684 if (!this->SatL || !this->SatV) throw ValueError(format("The saturation properties are needed for the two-phase properties"));
2685 if (std::abs(_Q) < DBL_EPSILON) {
2686 _smolar = SatL->smolar();
2687 } else if (std::abs(_Q - 1) < DBL_EPSILON) {
2688 _smolar = SatV->smolar();
2689 } else {
2690 _smolar = _Q * SatV->smolar() + (1 - _Q) * SatL->smolar();
2691 }
2692 return static_cast<CoolPropDbl>(_smolar);
2693 } else if (isHomogeneousPhase()) {
2694 // Calculate the reducing parameters
2696 _tau = _reducing.T / _T;
2697
2698 // Calculate derivatives if needed, or just use cached values
2699 CoolPropDbl da0_dTau = dalpha0_dTau();
2700 CoolPropDbl ar = alphar();
2701 CoolPropDbl a0 = alpha0();
2702 CoolPropDbl dar_dTau = dalphar_dTau();
2703 CoolPropDbl R_u = gas_constant();
2704
2705 // Get molar entropy
2706 _smolar = R_u * (_tau.pt() * (da0_dTau + dar_dTau) - a0 - ar);
2707
2708 return static_cast<CoolPropDbl>(_smolar);
2709 } else {
2710 throw ValueError(format("phase is invalid in calc_smolar"));
2711 }
2712}
2714 // Calculate the reducing parameters
2717
2718 // Calculate derivatives
2721 CoolPropDbl R_u = gas_constant();
2722
2723 // Get molar internal energy
2724 return R_u * T * tau * (da0_dTau + dar_dTau);
2725}
2727 if (isTwoPhase()) {
2728 if (!this->SatL || !this->SatV) throw ValueError(format("The saturation properties are needed for the two-phase properties"));
2729 if (std::abs(_Q) < DBL_EPSILON) {
2730 _umolar = SatL->umolar();
2731 } else if (std::abs(_Q - 1) < DBL_EPSILON) {
2732 _umolar = SatV->umolar();
2733 } else {
2734 _umolar = _Q * SatV->umolar() + (1 - _Q) * SatL->umolar();
2735 }
2736 return static_cast<CoolPropDbl>(_umolar);
2737 } else if (isHomogeneousPhase()) {
2738 // Calculate the reducing parameters
2740 _tau = _reducing.T / _T;
2741
2742 // Calculate derivatives if needed, or just use cached values
2743 CoolPropDbl da0_dTau = dalpha0_dTau();
2744 CoolPropDbl dar_dTau = dalphar_dTau();
2745 CoolPropDbl R_u = gas_constant();
2746
2747 // Get molar internal energy
2748 _umolar = R_u * _T * _tau.pt() * (da0_dTau + dar_dTau);
2749
2750 return static_cast<CoolPropDbl>(_umolar);
2751 } else {
2752 throw ValueError(format("phase is invalid in calc_umolar"));
2753 }
2754}
2756 // Calculate the reducing parameters
2758 _tau = _reducing.T / _T;
2759
2760 // Calculate derivatives if needed, or just use cached values
2761 CoolPropDbl d2ar_dTau2 = d2alphar_dTau2();
2762 CoolPropDbl d2a0_dTau2 = d2alpha0_dTau2();
2763 CoolPropDbl R_u = gas_constant();
2764
2765 // Get cv
2766 _cvmolar = -R_u * pow(_tau.pt(), 2) * (d2ar_dTau2 + d2a0_dTau2);
2767
2768 return static_cast<double>(_cvmolar);
2769}
2771 // Calculate the reducing parameters
2773 _tau = _reducing.T / _T;
2774
2775 // Calculate derivatives if needed, or just use cached values
2776 CoolPropDbl d2a0_dTau2 = d2alpha0_dTau2();
2777 CoolPropDbl dar_dDelta = dalphar_dDelta();
2778 CoolPropDbl d2ar_dDelta2 = d2alphar_dDelta2();
2779 CoolPropDbl d2ar_dDelta_dTau = d2alphar_dDelta_dTau();
2780 CoolPropDbl d2ar_dTau2 = d2alphar_dTau2();
2781 CoolPropDbl R_u = gas_constant();
2782
2783 // Get cp
2784 _cpmolar = R_u
2785 * (-pow(_tau.pt(), 2) * (d2ar_dTau2 + d2a0_dTau2)
2786 + pow(1 + _delta.pt() * dar_dDelta - _delta.pt() * _tau.pt() * d2ar_dDelta_dTau, 2)
2787 / (1 + 2 * _delta.pt() * dar_dDelta + pow(_delta.pt(), 2) * d2ar_dDelta2));
2788
2789 return static_cast<double>(_cpmolar);
2790}
2792 // Calculate the reducing parameters
2794 _tau = _reducing.T / _T;
2795
2796 // Calculate derivatives if needed, or just use cached values
2797 CoolPropDbl d2a0_dTau2 = d2alpha0_dTau2();
2798 CoolPropDbl R_u = gas_constant();
2799
2800 // Get cp of the ideal gas
2801 return R_u * (1 + (-pow(_tau.pt(), 2)) * d2a0_dTau2);
2802}
2804 if (isTwoPhase()) {
2805 if (std::abs(_Q) < DBL_EPSILON) {
2806 return SatL->speed_sound();
2807 } else if (std::abs(_Q - 1) < DBL_EPSILON) {
2808 return SatV->speed_sound();
2809 } else {
2810 throw ValueError(format("Speed of sound is not defined for two-phase states because it depends on the distribution of phases."));
2811 }
2812 } else if (isHomogeneousPhase()) {
2813 // Calculate the reducing parameters
2815 _tau = _reducing.T / _T;
2816
2817 // Calculate derivatives if needed, or just use cached values
2818 CoolPropDbl d2a0_dTau2 = d2alpha0_dTau2();
2819 CoolPropDbl dar_dDelta = dalphar_dDelta();
2820 CoolPropDbl d2ar_dDelta2 = d2alphar_dDelta2();
2821 CoolPropDbl d2ar_dDelta_dTau = d2alphar_dDelta_dTau();
2822 CoolPropDbl d2ar_dTau2 = d2alphar_dTau2();
2823 CoolPropDbl R_u = gas_constant();
2824 CoolPropDbl mm = molar_mass();
2825
2826 // Get speed of sound
2827 _speed_sound = sqrt(
2828 R_u * _T / mm
2829 * (1 + 2 * _delta.pt() * dar_dDelta + pow(_delta.pt(), 2) * d2ar_dDelta2
2830 - pow(1 + _delta.pt() * dar_dDelta - _delta.pt() * _tau.pt() * d2ar_dDelta_dTau, 2) / (pow(_tau.pt(), 2) * (d2ar_dTau2 + d2a0_dTau2))));
2831
2832 return static_cast<CoolPropDbl>(_speed_sound);
2833 } else {
2834 throw ValueError(format("phase is invalid in calc_speed_sound"));
2835 }
2836}
2837
2839 // Calculate the reducing parameters
2842
2843 // Calculate derivatives
2847
2848 // Get molar gibbs function
2849 return gas_constant() * T * (1 + a0 + ar + delta * dar_dDelta);
2850}
2852 if (isTwoPhase()) {
2853 if (!this->SatL || !this->SatV) throw ValueError(format("The saturation properties are needed for the two-phase properties"));
2854 _gibbsmolar = _Q * SatV->gibbsmolar() + (1 - _Q) * SatL->gibbsmolar();
2855 return static_cast<CoolPropDbl>(_gibbsmolar);
2856 } else if (isHomogeneousPhase()) {
2857 // Calculate the reducing parameters
2859 _tau = _reducing.T / _T;
2860
2861 // Calculate derivatives if needed, or just use cached values
2862 CoolPropDbl ar = alphar();
2863 CoolPropDbl a0 = alpha0();
2864 CoolPropDbl dar_dDelta = dalphar_dDelta();
2865 CoolPropDbl R_u = gas_constant();
2866
2867 // Get molar gibbs function
2868 _gibbsmolar = R_u * _T * (1 + a0 + ar + _delta.pt() * dar_dDelta);
2869
2870 return static_cast<CoolPropDbl>(_gibbsmolar);
2871 } else {
2872 throw ValueError(format("phase is invalid in calc_gibbsmolar"));
2873 }
2874}
2877 _umolar_excess = this->umolar();
2878 _volumemolar_excess = 1 / this->rhomolar();
2879 for (std::size_t i = 0; i < components.size(); ++i) {
2881 transient_pure_state->update(PT_INPUTS, p(), T());
2882 double x_i = mole_fractions[i];
2883 double R = gas_constant();
2884 _gibbsmolar_excess = static_cast<double>(_gibbsmolar_excess) - x_i * (transient_pure_state->gibbsmolar() + R * T() * log(x_i));
2885 _hmolar_excess = static_cast<double>(_hmolar_excess) - x_i * transient_pure_state->hmolar();
2886 _umolar_excess = static_cast<double>(_umolar_excess) - x_i * transient_pure_state->umolar();
2887 _smolar_excess = static_cast<double>(_smolar_excess) - x_i * (transient_pure_state->smolar() - R * log(x_i));
2888 _volumemolar_excess = static_cast<double>(_volumemolar_excess) - x_i / transient_pure_state->rhomolar();
2889 }
2890 _helmholtzmolar_excess = static_cast<double>(_umolar_excess) - _T * static_cast<double>(_smolar_excess);
2891}
2892
2894 if (isTwoPhase()) {
2895 if (!this->SatL || !this->SatV) throw ValueError(format("The saturation properties are needed for the two-phase properties"));
2896 _helmholtzmolar = _Q * SatV->helmholtzmolar() + (1 - _Q) * SatL->helmholtzmolar();
2897 return static_cast<CoolPropDbl>(_helmholtzmolar);
2898 } else if (isHomogeneousPhase()) {
2899 // Calculate the reducing parameters
2901 _tau = _reducing.T / _T;
2902
2903 // Calculate derivatives if needed, or just use cached values
2904 CoolPropDbl ar = alphar();
2905 CoolPropDbl a0 = alpha0();
2906 CoolPropDbl R_u = gas_constant();
2907
2908 // Get molar Helmholtz energy
2909 _helmholtzmolar = R_u * _T * (a0 + ar);
2910
2911 return static_cast<CoolPropDbl>(_helmholtzmolar);
2912 } else {
2913 throw ValueError(format("phase is invalid in calc_helmholtzmolar"));
2914 }
2915}
2918 return exp(MixtureDerivatives::ln_fugacity_coefficient(*this, i, xN_flag));
2919}
2922 return MixtureDerivatives::fugacity_i(*this, i, xN_flag);
2923}
2926 double Tci = get_fluid_constant(i, iT_critical);
2927 double rhoci = get_fluid_constant(i, irhomolar_critical);
2928 double dnar_dni__const_T_V_nj = MixtureDerivatives::dnalphar_dni__constT_V_nj(*this, i, xN_flag);
2929 double dna0_dni__const_T_V_nj =
2930 components[i].EOS().alpha0.base(tau() * (Tci / T_reducing()), delta() / (rhoci / rhomolar_reducing())) + 1 + log(mole_fractions[i]);
2931 return gas_constant() * T() * (dna0_dni__const_T_V_nj + dnar_dni__const_T_V_nj);
2932}
2934 return 2
2935 - rhomolar()
2938}
2939
2940SimpleState HelmholtzEOSMixtureBackend::calc_reducing_state_nocache(const std::vector<CoolPropDbl>& mole_fractions) {
2941 SimpleState reducing;
2943 reducing = components[0].EOS().reduce;
2944 } else {
2945 reducing.T = Reducing->Tr(mole_fractions);
2946 reducing.rhomolar = Reducing->rhormolar(mole_fractions);
2947 }
2948 return reducing;
2949}
2951 if (get_mole_fractions_ref().empty()) {
2952 throw ValueError("Mole fractions must be set before calling calc_reducing_state");
2953 }
2956 _crit = _reducing;
2957}
2958void HelmholtzEOSMixtureBackend::calc_all_alphar_deriv_cache(const std::vector<CoolPropDbl>& mole_fractions, const CoolPropDbl& tau,
2959 const CoolPropDbl& delta) {
2960 deriv_counter++;
2961 bool cache_values = true;
2962 HelmholtzDerivatives derivs = residual_helmholtz->all(*this, get_mole_fractions_ref(), tau, delta, cache_values);
2963 _alphar = derivs.alphar;
2964 _dalphar_dDelta = derivs.dalphar_ddelta;
2965 _dalphar_dTau = derivs.dalphar_dtau;
2966 _d2alphar_dDelta2 = derivs.d2alphar_ddelta2;
2967 _d2alphar_dDelta_dTau = derivs.d2alphar_ddelta_dtau;
2968 _d2alphar_dTau2 = derivs.d2alphar_dtau2;
2969 _d3alphar_dDelta3 = derivs.d3alphar_ddelta3;
2970 _d3alphar_dDelta2_dTau = derivs.d3alphar_ddelta2_dtau;
2971 _d3alphar_dDelta_dTau2 = derivs.d3alphar_ddelta_dtau2;
2972 _d3alphar_dTau3 = derivs.d3alphar_dtau3;
2973 _d4alphar_dDelta4 = derivs.d4alphar_ddelta4;
2974 _d4alphar_dDelta3_dTau = derivs.d4alphar_ddelta3_dtau;
2975 _d4alphar_dDelta2_dTau2 = derivs.d4alphar_ddelta2_dtau2;
2976 _d4alphar_dDelta_dTau3 = derivs.d4alphar_ddelta_dtau3;
2977 _d4alphar_dTau4 = derivs.d4alphar_dtau4;
2978}
2979
2980CoolPropDbl HelmholtzEOSMixtureBackend::calc_alphar_deriv_nocache(const int nTau, const int nDelta, const std::vector<CoolPropDbl>& mole_fractions,
2981 const CoolPropDbl& tau, const CoolPropDbl& delta) {
2982 bool cache_values = false;
2983 HelmholtzDerivatives derivs = residual_helmholtz->all(*this, mole_fractions, tau, delta, cache_values);
2984 return derivs.get(nTau, nDelta);
2985}
2986CoolPropDbl HelmholtzEOSMixtureBackend::calc_alpha0_deriv_nocache(const int nTau, const int nDelta, const std::vector<CoolPropDbl>& mole_fractions,
2987 const CoolPropDbl& tau, const CoolPropDbl& delta, const CoolPropDbl& Tr,
2988 const CoolPropDbl& rhor) {
2989 CoolPropDbl val;
2990 if (components.size() == 0) {
2991 throw ValueError("No alpha0 derivatives are available");
2992 }
2994 EquationOfState& E = components[0].EOS();
2995 // In the case of cubics, we need to use the shifted tau^*=Tc/T and delta^*=rho/rhoc
2996 // rather than tau=Tr/T and delta=rho/rhor
2997 // For multiparameter EOS, this changes nothing because Tc/Tr = 1 and rhoc/rhor = 1
2999
3000 // Cache the reducing temperature in some terms that need it (GERG-2004 models)
3001 E.alpha0.set_Tred(Tc);
3002 double taustar = Tc / Tr * tau, deltastar = rhor / rhomolarc * delta;
3003 if (nTau == 0 && nDelta == 0) {
3004 val = E.base0(taustar, deltastar);
3005 } else if (nTau == 0 && nDelta == 1) {
3006 val = E.dalpha0_dDelta(taustar, deltastar);
3007 } else if (nTau == 1 && nDelta == 0) {
3008 val = E.dalpha0_dTau(taustar, deltastar);
3009 } else if (nTau == 0 && nDelta == 2) {
3010 val = E.d2alpha0_dDelta2(taustar, deltastar);
3011 } else if (nTau == 1 && nDelta == 1) {
3012 val = E.d2alpha0_dDelta_dTau(taustar, deltastar);
3013 } else if (nTau == 2 && nDelta == 0) {
3014 val = E.d2alpha0_dTau2(taustar, deltastar);
3015 } else if (nTau == 0 && nDelta == 3) {
3016 val = E.d3alpha0_dDelta3(taustar, deltastar);
3017 } else if (nTau == 1 && nDelta == 2) {
3018 val = E.d3alpha0_dDelta2_dTau(taustar, deltastar);
3019 } else if (nTau == 2 && nDelta == 1) {
3020 val = E.d3alpha0_dDelta_dTau2(taustar, deltastar);
3021 } else if (nTau == 3 && nDelta == 0) {
3022 val = E.d3alpha0_dTau3(taustar, deltastar);
3023 } else {
3024 throw ValueError();
3025 }
3026 val *= pow(rhor / rhomolarc, nDelta);
3027 val /= pow(Tr / Tc, nTau);
3028 if (!ValidNumber(val)) {
3029 //calc_alpha0_deriv_nocache(nTau,nDelta,mole_fractions,tau,delta,Tr,rhor);
3030 throw ValueError(format("calc_alpha0_deriv_nocache returned invalid number with inputs nTau: %d, nDelta: %d, tau: %Lg, delta: %Lg", nTau,
3031 nDelta, tau, delta));
3032 } else {
3033 return val;
3034 }
3035 } else {
3036 // See Table B5, GERG 2008 from Kunz Wagner, JCED, 2012
3037 std::size_t N = mole_fractions.size();
3038 CoolPropDbl summer = 0;
3039 CoolPropDbl tau_i, delta_i, rho_ci, T_ci;
3040 CoolPropDbl Rmix = gas_constant();
3041 for (unsigned int i = 0; i < N; ++i) {
3042
3046 tau_i = T_ci * tau / Tr;
3047 delta_i = delta * rhor / rho_ci;
3048 CoolPropDbl Rratio = Rcomponent / Rmix;
3049
3050 // Cache the reducing temperature in some terms that need it (GERG-2004 models)
3051 components[i].EOS().alpha0.set_Tred(Tr);
3052
3053 if (nTau == 0 && nDelta == 0) {
3054 double logxi = (std::abs(mole_fractions[i]) > DBL_EPSILON) ? log(mole_fractions[i]) : 0;
3055 summer += mole_fractions[i] * Rratio * (components[i].EOS().base0(tau_i, delta_i) + logxi);
3056 } else if (nTau == 0 && nDelta == 1) {
3057 summer += mole_fractions[i] * Rratio * rhor / rho_ci * components[i].EOS().dalpha0_dDelta(tau_i, delta_i);
3058 } else if (nTau == 1 && nDelta == 0) {
3059 summer += mole_fractions[i] * Rratio * T_ci / Tr * components[i].EOS().dalpha0_dTau(tau_i, delta_i);
3060 } else if (nTau == 0 && nDelta == 2) {
3061 summer += mole_fractions[i] * Rratio * pow(rhor / rho_ci, 2) * components[i].EOS().d2alpha0_dDelta2(tau_i, delta_i);
3062 } else if (nTau == 1 && nDelta == 1) {
3063 summer += mole_fractions[i] * Rratio * rhor / rho_ci * T_ci / Tr * components[i].EOS().d2alpha0_dDelta_dTau(tau_i, delta_i);
3064 } else if (nTau == 2 && nDelta == 0) {
3065 summer += mole_fractions[i] * Rratio * pow(T_ci / Tr, 2) * components[i].EOS().d2alpha0_dTau2(tau_i, delta_i);
3066 } else {
3067 throw ValueError();
3068 }
3069 }
3070 return summer;
3071 }
3072}
3075 return static_cast<CoolPropDbl>(_alphar);
3076}
3079 return static_cast<CoolPropDbl>(_dalphar_dDelta);
3080}
3083 return static_cast<CoolPropDbl>(_dalphar_dTau);
3084}
3087 return static_cast<CoolPropDbl>(_d2alphar_dTau2);
3088}
3091 return static_cast<CoolPropDbl>(_d2alphar_dDelta_dTau);
3092}
3095 return static_cast<CoolPropDbl>(_d2alphar_dDelta2);
3096}
3099 return static_cast<CoolPropDbl>(_d3alphar_dDelta3);
3100}
3103 return static_cast<CoolPropDbl>(_d3alphar_dDelta2_dTau);
3104}
3107 return static_cast<CoolPropDbl>(_d3alphar_dDelta_dTau2);
3108}
3111 return static_cast<CoolPropDbl>(_d3alphar_dTau3);
3112}
3113
3116 return static_cast<CoolPropDbl>(_d4alphar_dDelta4);
3117}
3120 return static_cast<CoolPropDbl>(_d4alphar_dDelta3_dTau);
3121}
3124 return static_cast<CoolPropDbl>(_d4alphar_dDelta2_dTau2);
3125}
3128 return static_cast<CoolPropDbl>(_d4alphar_dDelta_dTau3);
3129}
3132 return static_cast<CoolPropDbl>(_d4alphar_dTau4);
3133}
3134
3136 const int nTau = 0, nDelta = 0;
3138}
3140 const int nTau = 0, nDelta = 1;
3142}
3144 const int nTau = 1, nDelta = 0;
3146}
3148 const int nTau = 0, nDelta = 2;
3150}
3152 const int nTau = 1, nDelta = 1;
3154}
3156 const int nTau = 2, nDelta = 0;
3158}
3160 const int nTau = 0, nDelta = 3;
3162}
3164 const int nTau = 1, nDelta = 2;
3166}
3168 const int nTau = 2, nDelta = 1;
3170}
3172 const int nTau = 3, nDelta = 0;
3174}
3177 // Derivative of temperature w.r.t. pressure ALONG the saturation curve
3178 CoolPropDbl dTdP_sat = T() * (1 / SatV.rhomolar() - 1 / SatL.rhomolar()) / (SatV.hmolar() - SatL.hmolar());
3179
3180 // "Trivial" inputs
3181 if (Of1 == iT && Wrt1 == iP) {
3182 return dTdP_sat;
3183 } else if (Of1 == iP && Wrt1 == iT) {
3184 return 1 / dTdP_sat;
3185 }
3186 // Derivative taken with respect to T
3187 else if (Wrt1 == iT) {
3188 return first_partial_deriv(Of1, iT, iP) + first_partial_deriv(Of1, iP, iT) / dTdP_sat;
3189 }
3190 // Derivative taken with respect to p
3191 else if (Wrt1 == iP) {
3192 return first_partial_deriv(Of1, iP, iT) + first_partial_deriv(Of1, iT, iP) * dTdP_sat;
3193 } else {
3194 throw ValueError(
3195 format("Not possible to take first saturation derivative with respect to %s", get_parameter_information(Wrt1, "short").c_str()));
3196 }
3197}
3199 if (!this->SatL || !this->SatV) throw ValueError(format("The saturation properties are needed for calc_first_saturation_deriv"));
3200
3201 // Derivative of temperature w.r.t. pressure ALONG the saturation curve
3202 CoolPropDbl dTdP_sat = T() * (1 / SatV->rhomolar() - 1 / SatL->rhomolar()) / (SatV->hmolar() - SatL->hmolar());
3203
3204 // "Trivial" inputs
3205 if (Of1 == iT && Wrt1 == iP) {
3206 return dTdP_sat;
3207 } else if (Of1 == iP && Wrt1 == iT) {
3208 return 1 / dTdP_sat;
3209 }
3210 // Derivative taken with respect to T
3211 else if (Wrt1 == iT) {
3212 return first_partial_deriv(Of1, iT, iP) + first_partial_deriv(Of1, iP, iT) / dTdP_sat;
3213 }
3214 // Derivative taken with respect to p
3215 else if (Wrt1 == iP) {
3216 return first_partial_deriv(Of1, iP, iT) + first_partial_deriv(Of1, iT, iP) * dTdP_sat;
3217 } else {
3218 throw ValueError(
3219 format("Not possible to take first saturation derivative with respect to %s", get_parameter_information(Wrt1, "short").c_str()));
3220 }
3221}
3223 if (!this->SatL || !this->SatV) throw ValueError(format("The saturation properties are needed for calc_second_saturation_deriv"));
3224 if (Wrt1 == iP && Wrt2 == iP) {
3225 CoolPropDbl dydT_constp = this->first_partial_deriv(Of1, iT, iP);
3226 CoolPropDbl d2ydTdp = this->second_partial_deriv(Of1, iT, iP, iP, iT);
3227 CoolPropDbl d2ydp2_constT = this->second_partial_deriv(Of1, iP, iT, iP, iT);
3228 CoolPropDbl d2ydT2_constp = this->second_partial_deriv(Of1, iT, iP, iT, iP);
3229
3230 CoolPropDbl dTdp_along_sat = calc_first_saturation_deriv(iT, iP);
3231 CoolPropDbl dvdrhoL = -1 / POW2(SatL->rhomolar());
3232 CoolPropDbl dvdrhoV = -1 / POW2(SatV->rhomolar());
3233 CoolPropDbl DELTAv = 1 / SatV->rhomolar() - 1 / SatL->rhomolar();
3234 CoolPropDbl dDELTAv_dT_constp = dvdrhoV * SatV->first_partial_deriv(iDmolar, iT, iP) - dvdrhoL * SatL->first_partial_deriv(iDmolar, iT, iP);
3235 CoolPropDbl dDELTAv_dp_constT = dvdrhoV * SatV->first_partial_deriv(iDmolar, iP, iT) - dvdrhoL * SatL->first_partial_deriv(iDmolar, iP, iT);
3236 CoolPropDbl DELTAh = SatV->hmolar() - SatL->hmolar();
3237 CoolPropDbl dDELTAh_dT_constp = SatV->first_partial_deriv(iHmolar, iT, iP) - SatL->first_partial_deriv(iHmolar, iT, iP);
3238 CoolPropDbl dDELTAh_dp_constT = SatV->first_partial_deriv(iHmolar, iP, iT) - SatL->first_partial_deriv(iHmolar, iP, iT);
3239 CoolPropDbl ddT_dTdp_along_sat_constp = (DELTAh * (_T * dDELTAv_dT_constp + DELTAv) - _T * DELTAv * dDELTAh_dT_constp) / POW2(DELTAh);
3240 CoolPropDbl ddp_dTdp_along_sat_constT = (DELTAh * (_T * dDELTAv_dp_constT) - _T * DELTAv * dDELTAh_dp_constT) / POW2(DELTAh);
3241
3242 double ddp_dydpsigma = d2ydp2_constT + dydT_constp * ddp_dTdp_along_sat_constT + d2ydTdp * dTdp_along_sat;
3243 double ddT_dydpsigma = d2ydTdp + dydT_constp * ddT_dTdp_along_sat_constp + d2ydT2_constp * dTdp_along_sat;
3244 return ddp_dydpsigma + ddT_dydpsigma * dTdp_along_sat;
3245 } else {
3246 throw ValueError(format("Currently, only possible to take second saturation derivative w.r.t. P (both times)"));
3247 }
3248}
3249
3251 parameters Constant2) {
3252 if (!this->SatL || !this->SatV) throw ValueError(format("The saturation properties are needed for calc_second_two_phase_deriv"));
3253
3254 if (Of == iDmolar
3255 && ((Wrt1 == iHmolar && Constant1 == iP && Wrt2 == iP && Constant2 == iHmolar)
3256 || (Wrt2 == iHmolar && Constant2 == iP && Wrt1 == iP && Constant1 == iHmolar))) {
3257 parameters h_key = iHmolar, rho_key = iDmolar, p_key = iP;
3258 // taking the derivative of (drho/dv)*(dv/dh|p) with respect to p with h constant
3259 CoolPropDbl dv_dh_constp = calc_first_two_phase_deriv(rho_key, h_key, p_key) / (-POW2(rhomolar()));
3260 CoolPropDbl drhomolar_dp__consth = first_two_phase_deriv(rho_key, p_key, h_key);
3261
3262 // Calculate the derivative of dvdh|p with respect to p at constant h
3263 CoolPropDbl dhL_dp_sat = SatL->calc_first_saturation_deriv(h_key, p_key, *SatL, *SatV);
3264 CoolPropDbl dhV_dp_sat = SatV->calc_first_saturation_deriv(h_key, p_key, *SatL, *SatV);
3265 CoolPropDbl drhoL_dp_sat = SatL->calc_first_saturation_deriv(rho_key, p_key, *SatL, *SatV);
3266 CoolPropDbl drhoV_dp_sat = SatV->calc_first_saturation_deriv(rho_key, p_key, *SatL, *SatV);
3267 CoolPropDbl numerator = 1 / SatV->keyed_output(rho_key) - 1 / SatL->keyed_output(rho_key);
3268 CoolPropDbl denominator = SatV->keyed_output(h_key) - SatL->keyed_output(h_key);
3269 CoolPropDbl dnumerator = -1 / POW2(SatV->keyed_output(rho_key)) * drhoV_dp_sat + 1 / POW2(SatL->keyed_output(rho_key)) * drhoL_dp_sat;
3270 CoolPropDbl ddenominator = dhV_dp_sat - dhL_dp_sat;
3271 CoolPropDbl d_dvdh_dp__consth = (denominator * dnumerator - numerator * ddenominator) / POW2(denominator);
3272 return -POW2(rhomolar()) * d_dvdh_dp__consth + dv_dh_constp * (-2 * rhomolar()) * drhomolar_dp__consth;
3273 } else if (Of == iDmass
3274 && ((Wrt1 == iHmass && Constant1 == iP && Wrt2 == iP && Constant2 == iHmass)
3275 || (Wrt2 == iHmass && Constant2 == iP && Wrt1 == iP && Constant1 == iHmass))) {
3276 parameters h_key = iHmass, rho_key = iDmass, p_key = iP;
3277 CoolPropDbl rho = keyed_output(rho_key);
3278 // taking the derivative of (drho/dv)*(dv/dh|p) with respect to p with h constant
3279 CoolPropDbl dv_dh_constp = calc_first_two_phase_deriv(rho_key, h_key, p_key) / (-POW2(rho));
3280 CoolPropDbl drho_dp__consth = first_two_phase_deriv(rho_key, p_key, h_key);
3281
3282 // Calculate the derivative of dvdh|p with respect to p at constant h
3283 CoolPropDbl dhL_dp_sat = SatL->calc_first_saturation_deriv(h_key, p_key, *SatL, *SatV);
3284 CoolPropDbl dhV_dp_sat = SatV->calc_first_saturation_deriv(h_key, p_key, *SatL, *SatV);
3285 CoolPropDbl drhoL_dp_sat = SatL->calc_first_saturation_deriv(rho_key, p_key, *SatL, *SatV);
3286 CoolPropDbl drhoV_dp_sat = SatV->calc_first_saturation_deriv(rho_key, p_key, *SatL, *SatV);
3287 CoolPropDbl numerator = 1 / SatV->keyed_output(rho_key) - 1 / SatL->keyed_output(rho_key);
3288 CoolPropDbl denominator = SatV->keyed_output(h_key) - SatL->keyed_output(h_key);
3289 CoolPropDbl dnumerator = -1 / POW2(SatV->keyed_output(rho_key)) * drhoV_dp_sat + 1 / POW2(SatL->keyed_output(rho_key)) * drhoL_dp_sat;
3290 CoolPropDbl ddenominator = dhV_dp_sat - dhL_dp_sat;
3291 CoolPropDbl d_dvdh_dp__consth = (denominator * dnumerator - numerator * ddenominator) / POW2(denominator);
3292 return -POW2(rho) * d_dvdh_dp__consth + dv_dh_constp * (-2 * rho) * drho_dp__consth;
3293 } else {
3294 throw ValueError();
3295 }
3296}
3298 if (!this->SatL || !this->SatV) throw ValueError(format("The saturation properties are needed for calc_first_two_phase_deriv"));
3299 if (Of == iDmolar && Wrt == iHmolar && Constant == iP) {
3300 return -POW2(rhomolar()) * (1 / SatV->rhomolar() - 1 / SatL->rhomolar()) / (SatV->hmolar() - SatL->hmolar());
3301 } else if (Of == iDmass && Wrt == iHmass && Constant == iP) {
3302 return -POW2(rhomass()) * (1 / SatV->rhomass() - 1 / SatL->rhomass()) / (SatV->hmass() - SatL->hmass());
3303 } else if (Of == iDmolar && Wrt == iP && Constant == iHmolar) {
3304 // v = 1/rho; drhodv = -rho^2; dvdrho = -1/rho^2
3305 CoolPropDbl dvdrhoL = -1 / POW2(SatL->rhomolar());
3306 CoolPropDbl dvdrhoV = -1 / POW2(SatV->rhomolar());
3307 CoolPropDbl dvL_dp = dvdrhoL * SatL->calc_first_saturation_deriv(iDmolar, iP, *SatL, *SatV);
3308 CoolPropDbl dvV_dp = dvdrhoV * SatV->calc_first_saturation_deriv(iDmolar, iP, *SatL, *SatV);
3309 CoolPropDbl dhL_dp = SatL->calc_first_saturation_deriv(iHmolar, iP, *SatL, *SatV);
3310 CoolPropDbl dhV_dp = SatV->calc_first_saturation_deriv(iHmolar, iP, *SatL, *SatV);
3311 CoolPropDbl dxdp_h = (Q() * dhV_dp + (1 - Q()) * dhL_dp) / (SatL->hmolar() - SatV->hmolar());
3312 CoolPropDbl dvdp_h = dvL_dp + dxdp_h * (1 / SatV->rhomolar() - 1 / SatL->rhomolar()) + Q() * (dvV_dp - dvL_dp);
3313 return -POW2(rhomolar()) * dvdp_h;
3314 } else if (Of == iDmass && Wrt == iP && Constant == iHmass) {
3315 // v = 1/rho; drhodv = -rho^2; dvdrho = -1/rho^2
3316 CoolPropDbl dvdrhoL = -1 / POW2(SatL->rhomass());
3317 CoolPropDbl dvdrhoV = -1 / POW2(SatV->rhomass());
3318 CoolPropDbl dvL_dp = dvdrhoL * SatL->calc_first_saturation_deriv(iDmass, iP, *SatL, *SatV);
3319 CoolPropDbl dvV_dp = dvdrhoV * SatV->calc_first_saturation_deriv(iDmass, iP, *SatL, *SatV);
3320 CoolPropDbl dhL_dp = SatL->calc_first_saturation_deriv(iHmass, iP, *SatL, *SatV);
3321 CoolPropDbl dhV_dp = SatV->calc_first_saturation_deriv(iHmass, iP, *SatL, *SatV);
3322 CoolPropDbl dxdp_h = (Q() * dhV_dp + (1 - Q()) * dhL_dp) / (SatL->hmass() - SatV->hmass());
3323 CoolPropDbl dvdp_h = dvL_dp + dxdp_h * (1 / SatV->rhomass() - 1 / SatL->rhomass()) + Q() * (dvV_dp - dvL_dp);
3324 return -POW2(rhomass()) * dvdp_h;
3325 } else {
3326 throw ValueError("These inputs are not supported to calc_first_two_phase_deriv");
3327 }
3328}
3330 // Note: If you need all three values (drho_dh__p, drho_dp__h and rho_spline),
3331 // you should calculate drho_dp__h first to avoid duplicate calculations.
3332
3333 bool drho_dh__p = false;
3334 bool drho_dp__h = false;
3335 bool rho_spline = false;
3336
3337 if (Of == iDmolar && Wrt == iHmolar && Constant == iP) {
3338 drho_dh__p = true;
3340 } else if (Of == iDmass && Wrt == iHmass && Constant == iP) {
3342 } else if (Of == iDmolar && Wrt == iP && Constant == iHmolar) {
3343 drho_dp__h = true;
3345 } else if (Of == iDmass && Wrt == iP && Constant == iHmass) {
3347 }
3348 // Add the special case for the splined density
3349 else if (Of == iDmolar && Wrt == iDmolar && Constant == iDmolar) {
3350 rho_spline = true;
3351 if (_rho_spline) return _rho_spline;
3352 } else if (Of == iDmass && Wrt == iDmass && Constant == iDmass) {
3354 } else {
3355 throw ValueError("These inputs are not supported to calc_first_two_phase_deriv");
3356 }
3357
3358 if (!this->SatL || !this->SatV) throw ValueError(format("The saturation properties are needed for calc_first_two_phase_deriv_splined"));
3359 if (_Q > x_end) {
3360 throw ValueError(format("Q [%g] is greater than x_end [%Lg]", _Q, x_end).c_str());
3361 }
3362 if (_phase != iphase_twophase) {
3363 throw ValueError(format("state is not two-phase"));
3364 }
3365
3366 shared_ptr<HelmholtzEOSMixtureBackend> Liq(new HelmholtzEOSMixtureBackend(this->get_components())),
3368
3369 Liq->specify_phase(iphase_liquid);
3370 Liq->_Q = -1;
3371 Liq->update_DmolarT_direct(SatL->rhomolar(), SatL->T());
3372 End->update(QT_INPUTS, x_end, SatL->T());
3373
3374 CoolPropDbl Delta = Q() * (SatV->keyed_output(iHmolar) - SatL->keyed_output(iHmolar));
3375 CoolPropDbl Delta_end = End->keyed_output(iHmolar) - SatL->keyed_output(iHmolar);
3376
3377 // At the end of the zone to which spline is applied
3378 CoolPropDbl drho_dh_end = End->calc_first_two_phase_deriv(iDmolar, iHmolar, iP);
3379 CoolPropDbl rho_end = End->keyed_output(iDmolar);
3380
3381 // Faking single-phase
3382 CoolPropDbl rho_liq = Liq->keyed_output(iDmolar);
3383 CoolPropDbl drho_dh_liq__constp = Liq->first_partial_deriv(iDmolar, iHmolar, iP);
3384
3385 // Spline coordinates a, b, c, d
3386 CoolPropDbl Abracket = (2 * rho_liq - 2 * rho_end + Delta_end * (drho_dh_liq__constp + drho_dh_end));
3387 CoolPropDbl a = 1 / POW3(Delta_end) * Abracket;
3388 CoolPropDbl b = 3 / POW2(Delta_end) * (-rho_liq + rho_end) - 1 / Delta_end * (drho_dh_end + 2 * drho_dh_liq__constp);
3389 CoolPropDbl c = drho_dh_liq__constp;
3390 CoolPropDbl d = rho_liq;
3391
3392 // Either the spline value or drho/dh|p can be directly evaluated now
3393 _rho_spline = a * POW3(Delta) + b * POW2(Delta) + c * Delta + d;
3394 _drho_spline_dh__constp = 3 * a * POW2(Delta) + 2 * b * Delta + c;
3395 if (rho_spline) return _rho_spline;
3396 if (drho_dh__p) return _drho_spline_dh__constp;
3397
3398 // It's drho/dp|h
3399 // ... calculate some more things
3400
3401 // Derivatives *along* the saturation curve using the special internal method
3402 CoolPropDbl dhL_dp_sat = SatL->calc_first_saturation_deriv(iHmolar, iP, *SatL, *SatV);
3403 CoolPropDbl dhV_dp_sat = SatV->calc_first_saturation_deriv(iHmolar, iP, *SatL, *SatV);
3404 CoolPropDbl drhoL_dp_sat = SatL->calc_first_saturation_deriv(iDmolar, iP, *SatL, *SatV);
3405 CoolPropDbl drhoV_dp_sat = SatV->calc_first_saturation_deriv(iDmolar, iP, *SatL, *SatV);
3406 CoolPropDbl rhoV = SatV->keyed_output(iDmolar);
3407 CoolPropDbl rhoL = SatL->keyed_output(iDmolar);
3408 CoolPropDbl drho_dp_end = POW2(End->keyed_output(iDmolar)) * (x_end / POW2(rhoV) * drhoV_dp_sat + (1 - x_end) / POW2(rhoL) * drhoL_dp_sat);
3409
3410 // Faking single-phase
3411 //CoolPropDbl drho_dp__consth_liq = Liq->first_partial_deriv(iDmolar, iP, iHmolar);
3412 CoolPropDbl d2rhodhdp_liq = Liq->second_partial_deriv(iDmolar, iHmolar, iP, iP, iHmolar); // ?
3413
3414 // Derivatives at the end point
3415 // CoolPropDbl drho_dp__consth_end = End->calc_first_two_phase_deriv(iDmolar, iP, iHmolar);
3416 CoolPropDbl d2rhodhdp_end = End->calc_second_two_phase_deriv(iDmolar, iHmolar, iP, iP, iHmolar);
3417
3418 // Reminder:
3419 // Delta = Q()*(hV-hL) = h-hL
3420 // Delta_end = x_end*(hV-hL);
3421 CoolPropDbl d_Delta_dp__consth = -dhL_dp_sat;
3422 CoolPropDbl d_Delta_end_dp__consth = x_end * (dhV_dp_sat - dhL_dp_sat);
3423
3424 // First pressure derivative at constant h of the coefficients a,b,c,d
3425 // CoolPropDbl Abracket = (2*rho_liq - 2*rho_end + Delta_end * (drho_dh_liq__constp + drho_dh_end));
3426 CoolPropDbl d_Abracket_dp_consth = (2 * drhoL_dp_sat - 2 * drho_dp_end + Delta_end * (d2rhodhdp_liq + d2rhodhdp_end)
3427 + d_Delta_end_dp__consth * (drho_dh_liq__constp + drho_dh_end));
3428 CoolPropDbl da_dp = 1 / POW3(Delta_end) * d_Abracket_dp_consth + Abracket * (-3 / POW4(Delta_end) * d_Delta_end_dp__consth);
3429 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)
3430 + (1 / POW2(Delta_end) * d_Delta_end_dp__consth) * (drho_dh_end + 2 * drho_dh_liq__constp)
3431 - (1 / Delta_end) * (d2rhodhdp_end + 2 * d2rhodhdp_liq);
3432 CoolPropDbl dc_dp = d2rhodhdp_liq;
3433 CoolPropDbl dd_dp = drhoL_dp_sat;
3434
3436 (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;
3437 if (drho_dp__h) return _drho_spline_dp__consth;
3438
3439 throw ValueError("Something went wrong in HelmholtzEOSMixtureBackend::calc_first_two_phase_deriv_splined");
3440 return _HUGE;
3441}
3442
3444 class Resid : public FuncWrapperND
3445 {
3446 public:
3448 double L1, M1;
3449 Eigen::MatrixXd Lstar, Mstar;
3450 Resid(HelmholtzEOSMixtureBackend& HEOS) : HEOS(HEOS), L1(_HUGE), M1(_HUGE){};
3451 std::vector<double> call(const std::vector<double>& tau_delta) {
3452 double rhomolar = tau_delta[1] * HEOS.rhomolar_reducing();
3453 double T = HEOS.T_reducing() / tau_delta[0];
3456 Mstar = MixtureDerivatives::Mstar(HEOS, XN_INDEPENDENT, Lstar);
3457 std::vector<double> o(2);
3458 o[0] = Lstar.determinant();
3459 o[1] = Mstar.determinant();
3460 return o;
3461 };
3462 std::vector<std::vector<double>> Jacobian(const std::vector<double>& x) {
3463 std::size_t N = x.size();
3464 std::vector<std::vector<double>> J(N, std::vector<double>(N, 0));
3465 Eigen::MatrixXd adjL = adjugate(Lstar), adjM = adjugate(Mstar), dLdTau = MixtureDerivatives::dLstar_dX(HEOS, XN_INDEPENDENT, iTau),
3467 dMdTau = MixtureDerivatives::dMstar_dX(HEOS, XN_INDEPENDENT, iTau, Lstar, dLdTau),
3468 dMdDelta = MixtureDerivatives::dMstar_dX(HEOS, XN_INDEPENDENT, iDelta, Lstar, dLdDelta);
3469
3470 J[0][0] = (adjL * dLdTau).trace();
3471 J[0][1] = (adjL * dLdDelta).trace();
3472 J[1][0] = (adjM * dMdTau).trace();
3473 J[1][1] = (adjM * dMdDelta).trace();
3474 return J;
3475 }
3477 std::vector<std::vector<double>> numerical_Jacobian(const std::vector<double>& x) {
3478 std::size_t N = x.size();
3479 std::vector<double> rplus, rminus, xp;
3480 std::vector<std::vector<double>> J(N, std::vector<double>(N, 0));
3481
3482 double epsilon = 0.0001;
3483
3484 // Build the Jacobian by column
3485 for (std::size_t i = 0; i < N; ++i) {
3486 xp = x;
3487 xp[i] += epsilon;
3488 rplus = call(xp);
3489 xp[i] -= 2 * epsilon;
3490 rminus = call(xp);
3491
3492 for (std::size_t j = 0; j < N; ++j) {
3493 J[j][i] = (rplus[j] - rminus[j]) / (2 * epsilon);
3494 }
3495 }
3496 std::cout << J[0][0] << " " << J[0][1] << std::endl;
3497 std::cout << J[1][0] << " " << J[1][1] << std::endl;
3498 return J;
3499 };
3500 };
3501 Resid resid(*this);
3502 std::vector<double> x, tau_delta(2);
3503 tau_delta[0] = T_reducing() / T0;
3504 tau_delta[1] = rho0 / rhomolar_reducing();
3505 x = NDNewtonRaphson_Jacobian(&resid, tau_delta, 1e-10, 100);
3506 _critical.T = T_reducing() / x[0];
3509
3510 CriticalState critical;
3511 critical.T = _critical.T;
3512 critical.p = _critical.p;
3513 critical.rhomolar = _critical.rhomolar;
3514 if (_critical.p < 0) {
3515 critical.stable = false;
3516 } else {
3517 if (get_config_bool(ASSUME_CRITICAL_POINT_STABLE)) {
3518 critical.stable = true;
3519 } else {
3520 // Otherwise we try to check stability with TPD-based analysis
3521 StabilityRoutines::StabilityEvaluationClass stability_tester(*this);
3522 critical.stable = stability_tester.is_stable();
3523 }
3524 }
3525 return critical;
3526}
3527
3532{
3533 public:
3535 const double delta;
3537 OneDimObjective(HelmholtzEOSMixtureBackend& HEOS, double delta0) : HEOS(HEOS), delta(delta0), _call(_HUGE), _deriv(_HUGE), _second_deriv(_HUGE){};
3538 double call(double tau) {
3539 double rhomolar = HEOS.rhomolar_reducing() * delta, T = HEOS.T_reducing() / tau;
3540 HEOS.update_DmolarT_direct(rhomolar, T);
3542 return _call;
3543 }
3544 double deriv(double tau) {
3545 Eigen::MatrixXd adjL = adjugate(MixtureDerivatives::Lstar(HEOS, XN_INDEPENDENT)),
3547 _deriv = (adjL * dLdTau).trace();
3548 return _deriv;
3549 };
3550 double second_deriv(double tau) {
3551 Eigen::MatrixXd Lstar = MixtureDerivatives::Lstar(HEOS, XN_INDEPENDENT),
3553 d2LstardTau2 = MixtureDerivatives::d2Lstar_dX2(HEOS, XN_INDEPENDENT, iTau, iTau), adjL = adjugate(Lstar),
3554 dadjLstardTau = adjugate_derivative(Lstar, dLstardTau);
3555 _second_deriv = (dLstardTau * dadjLstardTau + adjL * d2LstardTau2).trace();
3556 return _second_deriv;
3557 };
3558};
3559
3563{
3564 public:
3566 double delta, tau,
3573 std::vector<CoolProp::CriticalState> critical_points;
3577 bool
3579 L0CurveTracer(HelmholtzEOSMixtureBackend& HEOS, double tau0, double delta0)
3580 : HEOS(HEOS), delta(delta0), tau(tau0), M1_last(_HUGE), N_critical_points(0), find_critical_points(true) {
3581 R_delta_tracer = 0.1;
3583 R_tau_tracer = 0.1;
3585 };
3586 /***
3587 \brief Update values for tau, delta
3588 @param theta The angle
3589 @param tau The old value of tau
3590 @param delta The old value of delta
3591 @param tau_new The new value of tau
3592 @param delta_new The new value of delta
3593 */
3594 void get_tau_delta(const double theta, const double tau, const double delta, double& tau_new, double& delta_new) {
3595 tau_new = tau + R_tau * cos(theta);
3596 delta_new = delta + R_delta * sin(theta);
3597 };
3598 /***
3599 \brief Calculate the value of L1
3600 @param theta The angle
3601 */
3602 double call(double theta) {
3603 double tau_new, delta_new;
3604 this->get_tau_delta(theta, tau, delta, tau_new, delta_new);
3605 double rhomolar = HEOS.rhomolar_reducing() * delta_new, T = HEOS.T_reducing() / tau_new;
3606 HEOS.update_DmolarT_direct(rhomolar, T);
3608 adjLstar = adjugate(Lstar);
3611 double L1 = Lstar.determinant();
3612 return L1;
3613 };
3614 /***
3615 \brief Calculate the first partial derivative of L1 with respect to the angle
3616 @param theta The angle
3617 */
3618 double deriv(double theta) {
3619 double dL1_dtau = (adjLstar * dLstardTau).trace(), dL1_ddelta = (adjLstar * dLstardDelta).trace();
3620 return -R_tau * sin(theta) * dL1_dtau + R_delta * cos(theta) * dL1_ddelta;
3621 };
3622
3623 void trace() {
3624 bool debug = (get_debug_level() > 0) | false;
3625 double theta;
3626 for (int i = 0; i < 300; ++i) {
3627 if (i == 0) {
3628 // In the first iteration, search all angles in the positive delta direction using a
3629 // bounded solver with a very small radius in order to not hit other L1*=0 contours
3630 // that are in the vicinity
3631 R_tau = 0.001;
3632 R_delta = 0.001;
3633 theta = Brent(this, 0, M_PI, DBL_EPSILON, 1e-10, 100);
3634 } else {
3635 // In subsequent iterations, you already have an excellent guess for the direction to
3636 // be searching in, use Newton's method to refine the solution since we also
3637 // have an analytic solution for the derivative
3640 try {
3641 theta = Newton(this, theta_last, 1e-10, 100);
3642 } catch (...) {
3643 if (N_critical_points > 0 && delta > 1.5 * critical_points[0].rhomolar / HEOS.rhomolar_reducing()) {
3644 // Stopping the search - probably we have a kink in the L1*=0 contour
3645 // caused by poor low-temperature behavior
3646 continue;
3647 }
3648 }
3649
3650 // If the solver takes a U-turn, going in the opposite direction of travel
3651 // this is not a good thing, and force a Brent's method solver call to make sure we keep
3652 // tracing in the same direction
3653 if (std::abs(angle_difference(theta, theta_last)) > M_PI / 2.0) {
3654 // We have found at least one critical point and we are now well above the density
3655 // associated with the first critical point
3656 if (N_critical_points > 0 && delta > 1.2 * critical_points[0].rhomolar / HEOS.rhomolar_reducing()) {
3657 // Stopping the search - probably we have a kink in the L1*=0 contour
3658 // caused by poor low-temperature behavior
3659 continue;
3660 } else {
3661 theta = Brent(this, theta_last - M_PI / 3.5, theta_last + M_PI / 3.5, DBL_EPSILON, 1e-10, 100);
3662 }
3663 }
3664 }
3665
3666 // Calculate the second criticality condition
3667 double M1 = MixtureDerivatives::Mstar(HEOS, XN_INDEPENDENT, Lstar).determinant();
3668 double p_MPa = HEOS.p() / 1e6;
3669
3670 // Calculate the new tau and delta at the new point
3671 double tau_new, delta_new;
3672 this->get_tau_delta(theta, tau, delta, tau_new, delta_new);
3673
3674 // Stop if bounds are exceeded
3675 if (p_MPa > 500 || HEOS.get_critical_is_terminated(delta_new, tau_new)) {
3676 break;
3677 }
3678
3679 // If the sign of M1 and the previous value of M1 have different signs, it means that
3680 // you have bracketed a critical point, run the full critical point solver to
3681 // find the critical point and store it
3682 // Only enabled if find_critical_points is true (the default)
3683 if (i > 0 && M1 * M1_last < 0 && find_critical_points) {
3684 double rhomolar = HEOS.rhomolar_reducing() * (delta + delta_new) / 2.0, T = HEOS.T_reducing() / ((tau + tau_new) / 2.0);
3686 critical_points.push_back(crit);
3688 if (debug) {
3689 std::cout << HEOS.get_mole_fractions()[0] << " " << crit.rhomolar << " " << crit.T << " " << p_MPa << std::endl;
3690 }
3691 }
3692
3693 // Update the storage values
3694 this->tau = tau_new;
3695 this->delta = delta_new;
3696 this->M1_last = M1;
3697 this->theta_last = theta;
3698
3699 this->spinodal_values.tau.push_back(tau_new);
3700 this->spinodal_values.delta.push_back(delta_new);
3701 this->spinodal_values.M1.push_back(M1);
3702 }
3703 };
3704};
3705
3707 Eigen::MatrixXd Lstar = MixtureDerivatives::Lstar(*this, XN_INDEPENDENT);
3708 Eigen::MatrixXd Mstar = MixtureDerivatives::Mstar(*this, XN_INDEPENDENT, Lstar);
3709 L1star = Lstar.determinant();
3710 M1star = Mstar.determinant();
3711};
3712
3714 R_delta = 0.025;
3715 R_tau = 0.1;
3716}
3717std::vector<CoolProp::CriticalState> HelmholtzEOSMixtureBackend::_calc_all_critical_points(bool find_critical_points) {
3718 // Populate the temporary class used to calculate the critical point(s)
3720 if (get_debug_level() > 10) {
3721 rapidjson::Document doc;
3722 doc.SetObject();
3723 rapidjson::Value& val = doc;
3724 std::vector<std::vector<DepartureFunctionPointer>>& mat = critical_state->residual_helmholtz->Excess.DepartureFunctionMatrix;
3725 if (mat.size() > 0) {
3726 mat[0][1]->phi.to_json(val, doc);
3727 std::cout << cpjson::to_string(doc);
3728 }
3729 }
3730 critical_state->set_mole_fractions(this->get_mole_fractions_ref());
3731
3732 // Specify state to be something homogeneous to shortcut phase evaluation
3733 critical_state->specify_phase(iphase_gas);
3734
3735 double delta0 = _HUGE, tau0 = _HUGE;
3736 critical_state->get_critical_point_starting_values(delta0, tau0);
3737
3738 OneDimObjective resid_L0(*critical_state, delta0);
3739
3740 // If the derivative of L1star with respect to tau is positive,
3741 // tau needs to be increased such that we sit on the other
3742 // side of the d(L1star)/dtau = 0 contour
3743 resid_L0.call(tau0);
3744 int bump_count = 0;
3745 while (resid_L0.deriv(tau0) > 0 && bump_count < 3) {
3746 tau0 *= 1.1;
3747 bump_count++;
3748 }
3749 double tau_L0 = Halley(resid_L0, tau0, 1e-10, 100);
3750 //double T0 = T_reducing()/tau_L0;
3751 //double rho0 = delta0*rhomolar_reducing();
3752
3753 L0CurveTracer tracer(*critical_state, tau_L0, delta0);
3754 tracer.find_critical_points = find_critical_points;
3755
3756 double R_delta = 0, R_tau = 0;
3757 critical_state->get_critical_point_search_radii(R_delta, R_tau);
3758 tracer.R_delta_tracer = R_delta;
3759 tracer.R_tau_tracer = R_tau;
3760 tracer.trace();
3761
3762 this->spinodal_values = tracer.spinodal_values;
3763
3764 return tracer.critical_points;
3765}
3766
3767double HelmholtzEOSMixtureBackend::calc_tangent_plane_distance(const double T, const double p, const std::vector<double>& w,
3768 const double rhomolar_guess) {
3769
3770 const std::vector<CoolPropDbl>& z = this->get_mole_fractions_ref();
3771 if (w.size() != z.size()) {
3772 throw ValueError(format("Trial composition vector size [%d] is not the same as bulk composition [%d]", w.size(), z.size()));
3773 }
3774 add_TPD_state();
3775 TPD_state->set_mole_fractions(w);
3776
3777 CoolPropDbl rho = TPD_state->solver_rho_Tp_global(T, p, 0.9 / TPD_state->SRK_covolume());
3778 TPD_state->update_DmolarT_direct(rho, T);
3779
3780 double summer = 0;
3781 for (std::size_t i = 0; i < w.size(); ++i) {
3782 summer +=
3784 }
3785 return summer;
3786}
3787
3789 // Ok, we are faking a little bit here, hijacking the code for critical points, but skipping evaluation of critical points
3790 bool find_critical_points = false;
3791 _calc_all_critical_points(find_critical_points);
3792}
3793
3794void HelmholtzEOSMixtureBackend::set_reference_stateS(const std::string& reference_state) {
3795 for (std::size_t i = 0; i < components.size(); ++i) {
3796 CoolProp::HelmholtzEOSMixtureBackend HEOS(std::vector<CoolPropFluid>(1, components[i]));
3797 if (!reference_state.compare("IIR")) {
3798 if (HEOS.Ttriple() > 273.15) {
3799 throw ValueError(format("Cannot use IIR reference state; Ttriple [%Lg] is greater than 273.15 K", HEOS.Ttriple()));
3800 }
3801 HEOS.update(QT_INPUTS, 0, 273.15);
3802
3803 // Get current values for the enthalpy and entropy
3804 double deltah = HEOS.hmass() - 200000; // offset from 200000 J/kg enthalpy
3805 double deltas = HEOS.smass() - 1000; // offset from 1000 J/kg/K entropy
3806 double delta_a1 = deltas / (HEOS.gas_constant() / HEOS.molar_mass());
3807 double delta_a2 = -deltah / (HEOS.gas_constant() / HEOS.molar_mass() * HEOS.get_reducing_state().T);
3808 // Change the value in the library for the given fluid
3809 set_fluid_enthalpy_entropy_offset(components[i], delta_a1, delta_a2, "IIR");
3810 if (get_debug_level() > 0) {
3811 std::cout << format("set offsets to %0.15g and %0.15g\n", delta_a1, delta_a2);
3812 }
3813 } else if (!reference_state.compare("ASHRAE")) {
3814 if (HEOS.Ttriple() > 233.15) {
3815 throw ValueError(format("Cannot use ASHRAE reference state; Ttriple [%Lg] is greater than than 233.15 K", HEOS.Ttriple()));
3816 }
3817 HEOS.update(QT_INPUTS, 0, 233.15);
3818
3819 // Get current values for the enthalpy and entropy
3820 double deltah = HEOS.hmass() - 0; // offset from 0 J/kg enthalpy
3821 double deltas = HEOS.smass() - 0; // offset from 0 J/kg/K entropy
3822 double delta_a1 = deltas / (HEOS.gas_constant() / HEOS.molar_mass());
3823 double delta_a2 = -deltah / (HEOS.gas_constant() / HEOS.molar_mass() * HEOS.get_reducing_state().T);
3824 // Change the value in the library for the given fluid
3825 set_fluid_enthalpy_entropy_offset(components[i], delta_a1, delta_a2, "ASHRAE");
3826 if (get_debug_level() > 0) {
3827 std::cout << format("set offsets to %0.15g and %0.15g\n", delta_a1, delta_a2);
3828 }
3829 } else if (!reference_state.compare("NBP")) {
3830 if (HEOS.p_triple() > 101325) {
3831 throw ValueError(format("Cannot use NBP reference state; p_triple [%Lg Pa] is greater than than 101325 Pa", HEOS.p_triple()));
3832 }
3833 // Saturated liquid boiling point at 1 atmosphere
3834 HEOS.update(PQ_INPUTS, 101325, 0);
3835
3836 double deltah = HEOS.hmass() - 0; // offset from 0 kJ/kg enthalpy
3837 double deltas = HEOS.smass() - 0; // offset from 0 kJ/kg/K entropy
3838 double delta_a1 = deltas / (HEOS.gas_constant() / HEOS.molar_mass());
3839 double delta_a2 = -deltah / (HEOS.gas_constant() / HEOS.molar_mass() * HEOS.get_reducing_state().T);
3840 // Change the value in the library for the given fluid
3841 set_fluid_enthalpy_entropy_offset(components[i], delta_a1, delta_a2, "NBP");
3842 if (get_debug_level() > 0) {
3843 std::cout << format("set offsets to %0.15g and %0.15g\n", delta_a1, delta_a2);
3844 }
3845 } else if (!reference_state.compare("DEF")) {
3847 } else if (!reference_state.compare("RESET")) {
3849 } else {
3850 throw ValueError(format("reference state string is invalid: [%s]", reference_state.c_str()));
3851 }
3852 }
3853}
3854
3860void HelmholtzEOSMixtureBackend::set_reference_stateD(double T, double rhomolar, double hmolar0, double smolar0) {
3861 for (std::size_t i = 0; i < components.size(); ++i) {
3862 CoolProp::HelmholtzEOSMixtureBackend HEOS(std::vector<CoolPropFluid>(1, components[i]));
3863
3865
3866 // Get current values for the enthalpy and entropy
3867 double deltah = HEOS.hmolar() - hmolar0; // offset from specified enthalpy in J/mol
3868 double deltas = HEOS.smolar() - smolar0; // offset from specified entropy in J/mol/K
3869 double delta_a1 = deltas / (HEOS.gas_constant());
3870 double delta_a2 = -deltah / (HEOS.gas_constant() * HEOS.get_reducing_state().T);
3871 set_fluid_enthalpy_entropy_offset(components[i], delta_a1, delta_a2, "custom");
3872 }
3873}
3874
3876 const std::string& ref) {
3877 component.EOS().alpha0.EnthalpyEntropyOffset.set(delta_a1, delta_a2, ref);
3878
3879 shared_ptr<CoolProp::HelmholtzEOSBackend> HEOS(new CoolProp::HelmholtzEOSBackend(component));
3880 HEOS->specify_phase(iphase_gas); // Something homogeneous;
3881 // Calculate the new enthalpy and entropy values
3882 HEOS->update(DmolarT_INPUTS, component.EOS().hs_anchor.rhomolar, component.EOS().hs_anchor.T);
3883 component.EOS().hs_anchor.hmolar = HEOS->hmolar();
3884 component.EOS().hs_anchor.smolar = HEOS->smolar();
3885
3886 double f = (HEOS->name() == "Water" || HEOS->name() == "CarbonDioxide") ? 1.00001 : 1.0;
3887
3888 // Calculate the new enthalpy and entropy values at the reducing state
3889 HEOS->update(DmolarT_INPUTS, component.EOS().reduce.rhomolar * f, component.EOS().reduce.T * f);
3890 component.EOS().reduce.hmolar = HEOS->hmolar();
3891 component.EOS().reduce.smolar = HEOS->smolar();
3892
3893 // Calculate the new enthalpy and entropy values at the critical state
3894 HEOS->update(DmolarT_INPUTS, component.crit.rhomolar * f, component.crit.T * f);
3895 component.crit.hmolar = HEOS->hmolar();
3896 component.crit.smolar = HEOS->smolar();
3897
3898 // Calculate the new enthalpy and entropy values
3899 HEOS->update(DmolarT_INPUTS, component.triple_liquid.rhomolar, component.triple_liquid.T);
3900 component.triple_liquid.hmolar = HEOS->hmolar();
3901 component.triple_liquid.smolar = HEOS->smolar();
3902
3903 // Calculate the new enthalpy and entropy values
3904 HEOS->update(DmolarT_INPUTS, component.triple_vapor.rhomolar, component.triple_vapor.T);
3905 component.triple_vapor.hmolar = HEOS->hmolar();
3906 component.triple_vapor.smolar = HEOS->smolar();
3907
3908 if (!HEOS->is_pure()) {
3909 // Calculate the new enthalpy and entropy values
3910 HEOS->update(DmolarT_INPUTS, component.EOS().max_sat_T.rhomolar, component.EOS().max_sat_T.T);
3911 component.EOS().max_sat_T.hmolar = HEOS->hmolar();
3912 component.EOS().max_sat_T.smolar = HEOS->smolar();
3913 // Calculate the new enthalpy and entropy values
3914 HEOS->update(DmolarT_INPUTS, component.EOS().max_sat_p.rhomolar, component.EOS().max_sat_p.T);
3915 component.EOS().max_sat_p.hmolar = HEOS->hmolar();
3916 component.EOS().max_sat_p.smolar = HEOS->smolar();
3917 }
3918}
3919
3920} /* namespace CoolProp */