CoolProp 6.8.1dev
An open-source fluid property and humid air property database
CoolProp.cpp
Go to the documentation of this file.
1#if defined(_MSC_VER)
2# ifndef _CRTDBG_MAP_ALLOC
3# define _CRTDBG_MAP_ALLOC
4# endif
5# ifndef _CRT_SECURE_NO_WARNINGS
6# define _CRT_SECURE_NO_WARNINGS
7# endif
8# include <crtdbg.h>
9#endif
10
11#include "CoolProp.h"
12#include "AbstractState.h"
13
14#if defined(__ISWINDOWS__)
15# include <windows.h>
16# ifdef min
17# undef min
18# endif
19# ifdef max
20# undef max
21# endif
22#else
23# ifndef DBL_EPSILON
24# include <limits>
25# define DBL_EPSILON std::numeric_limits<double>::epsilon()
26# endif
27#endif
28
29#include <memory>
30
31#include <iostream>
32#include <stdlib.h>
33#include <vector>
34#include <exception>
35#include <stdio.h>
36#include <string>
37#include <locale>
38#include "CoolPropTools.h"
39#include "Solvers.h"
40#include "MatrixMath.h"
46#include "DataStructures.h"
50
51#if defined(ENABLE_CATCH)
52# include <catch2/catch_all.hpp>
53#endif
54
55namespace CoolProp {
56
57static int debug_level = 0;
58static std::string error_string;
59static std::string warning_string;
60
61void set_debug_level(int level) {
62 debug_level = level;
63}
64int get_debug_level(void) {
65 return debug_level;
66}
67
69#include "gitrevision.h" // Contents are like "std::string gitrevision = "aa121435436ggregrea4t43t433";"
70#include "cpversion.h" // Contents are like "char version [] = "2.5";"
71
72void set_warning_string(const std::string& warning) {
73 warning_string = warning;
74}
75void set_error_string(const std::string& error) {
76 error_string = error;
77}
78
79// Return true if the string has "BACKEND::*" format where * signifies a wildcard
80bool has_backend_in_string(const std::string& fluid_string, std::size_t& i) {
81 i = fluid_string.find("::");
82 return i != std::string::npos;
83}
84
85void extract_backend(std::string fluid_string, std::string& backend, std::string& fluid) {
86 std::size_t i;
87 // For backwards compatibility reasons, if "REFPROP-" or "REFPROP-MIX:" start
88 // the fluid_string, replace them with "REFPROP::"
89 if (fluid_string.find("REFPROP-MIX:") == 0) {
90 fluid_string.replace(0, 12, "REFPROP::");
91 }
92 if (fluid_string.find("REFPROP-") == 0) {
93 fluid_string.replace(0, 8, "REFPROP::");
94 }
95 if (has_backend_in_string(fluid_string, i)) {
96 // Part without the ::
97 backend = fluid_string.substr(0, i);
98 // Fluid name after the ::
99 fluid = fluid_string.substr(i + 2);
100 } else {
101 backend = "?";
102 fluid = fluid_string;
103 }
104 if (get_debug_level() > 10)
105 std::cout << format("%s:%d: backend extracted. backend: %s. fluid: %s\n", __FILE__, __LINE__, backend.c_str(), fluid.c_str());
106}
107
108bool has_fractions_in_string(const std::string& fluid_string) {
109 // If can find both "[" and "]", it must have mole fractions encoded as string
110 return (fluid_string.find("[") != std::string::npos && fluid_string.find("]") != std::string::npos);
111}
112bool has_solution_concentration(const std::string& fluid_string) {
113 // If can find "-", expect mass fractions encoded as string
114 return (fluid_string.find('-') != std::string::npos && fluid_string.find('%') != std::string::npos);
115}
116
117struct delim : std::numpunct<char>
118{
119 char m_c;
120 delim(char c) : m_c(c){};
121 char do_decimal_point() const {
122 return m_c;
123 }
124};
125
126std::string extract_fractions(const std::string& fluid_string, std::vector<double>& fractions) {
127
128 if (has_fractions_in_string(fluid_string)) {
129 fractions.clear();
130 std::vector<std::string> names;
131
132 // Break up into pairs - like "Ethane[0.5]&Methane[0.5]" -> ("Ethane[0.5]","Methane[0.5]")
133 std::vector<std::string> pairs = strsplit(fluid_string, '&');
134
135 for (std::size_t i = 0; i < pairs.size(); ++i) {
136 const std::string& fluid = pairs[i];
137
138 // Must end with ']'
139 if (fluid[fluid.size() - 1] != ']') throw ValueError(format("Fluid entry [%s] must end with ']' character", pairs[i].c_str()));
140
141 // Split at '[', but first remove the ']' from the end by taking a substring
142 std::vector<std::string> name_fraction = strsplit(fluid.substr(0, fluid.size() - 1), '[');
143
144 if (name_fraction.size() != 2) {
145 throw ValueError(format("Could not break [%s] into name/fraction", fluid.substr(0, fluid.size() - 1).c_str()));
146 }
147
148 // Convert fraction to a double
149 const std::string &name = name_fraction[0], &fraction = name_fraction[1];
150 // The default locale for conversion from string to double is en_US with . as the deliminter
151 // Good: 0.1234 Bad: 0,1234
152 // But you can change the punctuation character for fraction parsing
153 // with the configuration variable FLOAT_PUNCTUATION to change the locale to something more convenient for you (e.g., a ',')
154 // See also http://en.cppreference.com/w/cpp/locale/numpunct/decimal_point
155 std::stringstream ssfraction(fraction);
156 char c = get_config_string(FLOAT_PUNCTUATION)[0];
157 ssfraction.imbue(std::locale(ssfraction.getloc(), new delim(c)));
158 double f;
159 ssfraction >> f;
160 if (ssfraction.rdbuf()->in_avail() != 0) {
161 throw ValueError(format("fraction [%s] was not converted fully", fraction.c_str()));
162 }
163
164 if (f > 1 || f < 0) {
165 throw ValueError(format("fraction [%s] was not converted to a value between 0 and 1 inclusive", fraction.c_str()));
166 }
167
168 if ((f > 10 * DBL_EPSILON) || // Only push component if fraction is positive and non-zero
169 (pairs.size() == 1)) // ..or if there is only one fluid (i.e. INCOMP backend )
170 {
171 // And add to vector
172 fractions.push_back(f);
173
174 // Add name
175 names.push_back(name);
176 }
177 }
178
179 if (get_debug_level() > 10)
180 std::cout << format("%s:%d: Detected fractions of %s for %s.", __FILE__, __LINE__, vec_to_string(fractions).c_str(),
181 (strjoin(names, "&")).c_str());
182 // Join fluids back together
183 return strjoin(names, "&");
184 } else if (has_solution_concentration(fluid_string)) {
185 fractions.clear();
186 double x;
187
188 std::vector<std::string> fluid_parts = strsplit(fluid_string, '-');
189 // Check it worked
190 if (fluid_parts.size() != 2) {
191 throw ValueError(
192 format("Format of incompressible solution string [%s] is invalid, should be like \"EG-20%\" or \"EG-0.2\" ", fluid_string.c_str()));
193 }
194
195 // Convert the concentration into a string
196 char* pEnd;
197 x = strtod(fluid_parts[1].c_str(), &pEnd);
198
199 // Check if per cent or fraction syntax is used
200 if (!strcmp(pEnd, "%")) {
201 x *= 0.01;
202 }
203 fractions.push_back(x);
204 if (get_debug_level() > 10)
205 std::cout << format("%s:%d: Detected incompressible concentration of %s for %s.", __FILE__, __LINE__, vec_to_string(fractions).c_str(),
206 fluid_parts[0].c_str());
207 return fluid_parts[0];
208 } else {
209 return fluid_string;
210 }
211}
212
213void _PropsSI_initialize(const std::string& backend, const std::vector<std::string>& fluid_names, const std::vector<double>& z,
214 shared_ptr<AbstractState>& State) {
215
216 if (fluid_names.empty()) {
217 throw ValueError("fluid_names cannot be empty");
218 }
219
220 std::vector<double> fractions(1, 1.0); // Default to one component, unity fraction
221 const std::vector<double>* fractions_ptr = NULL; // Pointer to the array to be used;
222
223 if (fluid_names.size() > 1) {
224 // Set the pointer - we are going to use the supplied fractions; they must be provided
225 fractions_ptr = &z;
226 // Reset the state
227 State.reset(AbstractState::factory(backend, fluid_names));
228 } else if (fluid_names.size() == 1) {
229 if (has_fractions_in_string(fluid_names[0]) || has_solution_concentration(fluid_names[0])) {
230 // Extract fractions from the string
231 std::string fluid_string = extract_fractions(fluid_names[0], fractions);
232 // Set the pointer - we are going to use the extracted fractions
233 fractions_ptr = &fractions;
234 // Reset the state
235 State.reset(AbstractState::factory(backend, fluid_string));
236 } else {
237 if (z.empty()) {
238 // Set the pointer - we are going to use the default fractions
239 fractions_ptr = &fractions;
240 } else {
241 // Set the pointer - we are going to use the provided fractions
242 fractions_ptr = &z;
243 }
244 // Reset the state
245 State.reset(AbstractState::factory(backend, fluid_names));
246 }
247 } else { // The only path where fractions_ptr stays NULL
248 throw ValueError("fractions_ptr is NULL");
249 }
250 if (!State->available_in_high_level()) {
251 throw ValueError(
252 "This AbstractState derived class cannot be used in the high-level interface; see www.coolprop.org/dev/coolprop/LowLevelAPI.html");
253 }
254
255 // Set the fraction for the state
256 if (State->using_mole_fractions()) {
257 // If a predefined mixture or a pure fluid, the fractions will already be set
258 if (State->get_mole_fractions().empty()) {
259 State->set_mole_fractions(*fractions_ptr);
260 }
261 } else if (State->using_mass_fractions()) {
262 State->set_mass_fractions(*fractions_ptr);
263 } else if (State->using_volu_fractions()) {
264 State->set_volu_fractions(*fractions_ptr);
265 } else {
266 if (get_debug_level() > 50)
267 std::cout << format("%s:%d: _PropsSI, could not set composition to %s, defaulting to mole fraction.\n", __FILE__, __LINE__,
268 vec_to_string(z).c_str())
269 .c_str();
270 }
271}
272
274{
276 {
283 };
288 static std::vector<output_parameter> get_output_parameters(const std::vector<std::string>& Outputs) {
289 std::vector<output_parameter> outputs;
290 for (std::vector<std::string>::const_iterator str = Outputs.begin(); str != Outputs.end(); ++str) {
292 CoolProp::parameters iOutput;
293 if (is_valid_parameter(*str, iOutput)) {
294 out.Of1 = iOutput;
295 if (is_trivial_parameter(iOutput)) {
297 } else {
299 }
300 } else if (is_valid_first_saturation_derivative(*str, out.Of1, out.Wrt1)) {
302 } else if (is_valid_first_derivative(*str, out.Of1, out.Wrt1, out.Constant1)) {
304 } else if (is_valid_second_derivative(*str, out.Of1, out.Wrt1, out.Constant1, out.Wrt2, out.Constant2)) {
306 } else {
307 throw ValueError(format("Output string is invalid [%s]", str->c_str()));
308 }
309 outputs.push_back(out);
310 }
311 return outputs;
312 };
313};
314
315void _PropsSI_outputs(shared_ptr<AbstractState>& State, const std::vector<output_parameter>& output_parameters, CoolProp::input_pairs input_pair,
316 const std::vector<double>& in1, const std::vector<double>& in2, std::vector<std::vector<double>>& IO) {
317
318 // Check the inputs
319 if (in1.size() != in2.size()) {
320 throw ValueError(format("lengths of in1 [%d] and in2 [%d] are not the same", in1.size(), in2.size()));
321 }
322 bool one_input_one_output = (in1.size() == 1 && in2.size() == 1 && output_parameters.size() == 1);
323 // If all trivial outputs, never do a state update
324 bool all_trivial_outputs = true;
325 for (std::size_t j = 0; j < output_parameters.size(); ++j) {
326 if (output_parameters[j].type != output_parameter::OUTPUT_TYPE_TRIVIAL) {
327 all_trivial_outputs = false;
328 }
329 }
330 parameters p1, p2;
331 // If all outputs are also inputs, never do a state update
332 bool all_outputs_in_inputs = true;
333 if (input_pair != INPUT_PAIR_INVALID) {
334 // Split the input pair into parameters
335 split_input_pair(input_pair, p1, p2);
336 // See if each parameter is in the output vector and is a normal type input
337 for (std::size_t j = 0; j < output_parameters.size(); ++j) {
338 if (output_parameters[j].type != output_parameter::OUTPUT_TYPE_NORMAL) {
339 all_outputs_in_inputs = false;
340 break;
341 }
342 if (!(output_parameters[j].Of1 == p1 || output_parameters[j].Of1 == p2)) {
343 all_outputs_in_inputs = false;
344 break;
345 }
346 }
347 } else {
348 if (!all_trivial_outputs) {
349 throw ValueError(format("Input pair variable is invalid and output(s) are non-trivial; cannot do state update"));
350 }
351 all_outputs_in_inputs = false;
352 }
353
354 if (get_debug_level() > 100) {
355 std::cout << format("%s (%d): input pair = %d ", __FILE__, __LINE__, input_pair) << std::endl;
356 std::cout << format("%s (%d): in1 = %s ", __FILE__, __LINE__, vec_to_string(in1).c_str()) << std::endl;
357 std::cout << format("%s (%d): in2 = %s ", __FILE__, __LINE__, vec_to_string(in2).c_str()) << std::endl;
358 }
359
360 // Get configuration variable for line tracing, see #1443
361 const bool use_guesses = get_config_bool(USE_GUESSES_IN_PROPSSI);
362 GuessesStructure guesses;
363
364 // Resize the output matrix
365 std::size_t N1 = std::max(static_cast<std::size_t>(1), in1.size());
366 std::size_t N2 = std::max(static_cast<std::size_t>(1), output_parameters.size());
367 IO.resize(N1, std::vector<double>(N2, _HUGE));
368
369 // Throw an error if at the end, there were no successes
370 bool success = false;
371 bool success_inner = false;
372
373 if (get_debug_level() > 100) {
374 std::cout << format("%s (%d): Iterating over %d input value pairs.", __FILE__, __LINE__, IO.size()) << std::endl;
375 }
376
377 // Iterate over the state variable inputs
378 for (std::size_t i = 0; i < IO.size(); ++i) {
379 // Reset the success indicator for the current state point
380 success_inner = false;
381 try {
382 if (input_pair != INPUT_PAIR_INVALID && !all_trivial_outputs && !all_outputs_in_inputs) {
383 // Update the state since it is a valid set of inputs
384 if (!use_guesses || i == 0) {
385 State->update(input_pair, in1[i], in2[i]);
386 } else {
387 State->update_with_guesses(input_pair, in1[i], in2[i], guesses);
388 guesses.clear();
389 }
390 }
391 } catch (...) {
392 if (one_input_one_output) {
393 IO.clear();
394 throw;
395 } // Re-raise the exception since we want to bubble the error
396 // All the outputs are filled with _HUGE; go to next input
397 for (std::size_t j = 0; j < IO[i].size(); ++j) {
398 IO[i][j] = _HUGE;
399 }
400 continue;
401 }
402
403 for (std::size_t j = 0; j < IO[i].size(); ++j) {
404 // If all the outputs are inputs, there is no need for a state input
405 if (all_outputs_in_inputs) {
406 if (p1 == output_parameters[j].Of1) {
407 IO[i][j] = in1[i];
408 success_inner = true;
409 continue;
410 } else if (p2 == output_parameters[j].Of1) {
411 IO[i][j] = in2[i];
412 success_inner = true;
413 continue;
414 } else {
415 throw ValueError();
416 }
417 }
418 try {
419 const output_parameter& output = output_parameters[j];
420 switch (output.type) {
423 IO[i][j] = State->keyed_output(output.Of1);
424 if (use_guesses) {
425 switch (output.Of1) {
426 case iDmolar:
427 guesses.rhomolar = IO[i][j];
428 break;
429 case iT:
430 guesses.T = IO[i][j];
431 break;
432 case iP:
433 guesses.p = IO[i][j];
434 break;
435 case iHmolar:
436 guesses.hmolar = IO[i][j];
437 break;
438 case iSmolar:
439 guesses.smolar = IO[i][j];
440 break;
441 default:
442 throw ValueError("Don't understand this parameter");
443 }
444 }
445 break;
447 IO[i][j] = State->first_partial_deriv(output.Of1, output.Wrt1, output.Constant1);
448 break;
450 IO[i][j] = State->first_saturation_deriv(output.Of1, output.Wrt1);
451 break;
453 IO[i][j] = State->second_partial_deriv(output.Of1, output.Wrt1, output.Constant1, output.Wrt2, output.Constant2);
454 break;
455 default:
456 throw ValueError(format(""));
457 break;
458 }
459 // At least one has succeeded
460 success_inner = true;
461 } catch (...) {
462 if (one_input_one_output) {
463 IO.clear();
464 throw;
465 } // Re-raise the exception since we want to bubble the error
466 IO[i][j] = _HUGE;
467 }
468 }
469 // We want to have at least rhomolar and T, but we do not raise errors here
470 if (use_guesses && success_inner) {
471 if (!ValidNumber(guesses.rhomolar)) {
472 try {
473 guesses.rhomolar = State->rhomolar();
474 } catch (...) {
475 guesses.rhomolar = _HUGE;
476 }
477 }
478 if (!ValidNumber(guesses.T)) {
479 try {
480 guesses.T = State->T();
481 } catch (...) {
482 guesses.T = _HUGE;
483 }
484 }
485 }
486 // Save the success indicator, just a single valid output is enough
487 success |= success_inner;
488 }
489 if (success == false) {
490 IO.clear();
491 throw ValueError(format("No outputs were able to be calculated"));
492 }
493}
494
495bool StripPhase(std::string& Name, shared_ptr<AbstractState>& State)
496// Parses an imposed phase out of the Input Name string using the "|" delimiter
497{
498 std::vector<std::string> strVec = strsplit(Name, '|'); // Split input key string in to vector containing input key [0] and phase string [1]
499 if (strVec.size() > 1) { // If there is a phase string (contains "|" character)
500 // Check for invalid backends for setting phase in PropsSI
501 std::string strBackend = State->backend_name();
502 if (strBackend == get_backend_string(INCOMP_BACKEND))
503 throw ValueError("Cannot set phase on Incompressible Fluid; always liquid phase"); // incompressible fluids are always "liquid".
504 if (strBackend == get_backend_string(IF97_BACKEND))
505 throw ValueError("Can't set phase on IF97 Backend"); // IF97 has to calculate it's own phase region
506 if (strBackend == get_backend_string(TTSE_BACKEND))
507 throw ValueError("Can't set phase on TTSE Backend in PropsSI"); // Shouldn't be calling from High-Level anyway
508 if (strBackend == get_backend_string(BICUBIC_BACKEND))
509 throw ValueError("Can't set phase on BICUBIC Backend in PropsSI"); // Shouldn't be calling from High-Level anyway
510 if (strBackend == get_backend_string(VTPR_BACKEND))
511 throw ValueError("Can't set phase on VTPR Backend in PropsSI"); // VTPR has no phase functions to call
512
513 phases imposed = iphase_not_imposed; // Initialize imposed phase
514 if (strVec.size() > 2) // If there's more than on phase separator, throw error
515 {
516 throw ValueError(format("Invalid phase format: \"%s\"", Name));
517 }
518 // Handle prefixes of iphase_, phase_, or <none>
519 std::basic_string<char>::iterator str_Iter;
520 std::string strPhase = strVec[1]; // Create a temp string so we can modify the prefix
521 if (strPhase.find("iphase_") != strPhase.npos) {
522 str_Iter = strPhase.erase(strPhase.begin());
523 } // Change "iphase_" to "phase_"
524 if (strPhase.find("phase_") == strPhase.npos) {
525 strPhase.insert(0, "phase_");
526 } // Prefix with "phase_" if missing
527 // See if phase is a valid phase string, updating imposed while we're at it...
528 if (!is_valid_phase(strPhase, imposed)) {
529 throw ValueError(format("Phase string \"%s\" is not a valid phase", strVec[1])); // throw error with original string if not valid
530 }
531 // Parsed phase string was valid
532 Name = strVec[0]; // Update input name to just the key string part
533 State->specify_phase(imposed); // Update the specified phase on the backend State
534 return true; // Return true because a valid phase string was found
535 }
536 return false; // Return false if there was no phase string on this key.
537}
538
539void _PropsSImulti(const std::vector<std::string>& Outputs, const std::string& Name1, const std::vector<double>& Prop1, const std::string& Name2,
540 const std::vector<double>& Prop2, const std::string& backend, const std::vector<std::string>& fluids,
541 const std::vector<double>& fractions, std::vector<std::vector<double>>& IO) {
542 shared_ptr<AbstractState> State;
543 CoolProp::parameters key1 = INVALID_PARAMETER, key2 = INVALID_PARAMETER; // Initialize to invalid parameter values
544 CoolProp::input_pairs input_pair = INPUT_PAIR_INVALID; // Initialize to invalid input pair
545 std::vector<output_parameter> output_parameters;
546 std::vector<double> v1, v2;
547
548 try {
549 // Initialize the State class
550 _PropsSI_initialize(backend, fluids, fractions, State);
551 } catch (std::exception& e) {
552 // Initialization failed. Stop.
553 throw ValueError(format("Initialize failed for backend: \"%s\", fluid: \"%s\" fractions \"%s\"; error: %s", backend.c_str(),
554 strjoin(fluids, "&").c_str(), vec_to_string(fractions, "%0.10f").c_str(), e.what()));
555 }
556
557 //strip any imposed phase from input key strings here
558 std::string N1 = Name1; // Make Non-constant copy of Name1 that we can modify
559 std::string N2 = Name2; // Make Non-constant copy of Name2 that we can modify
560 bool HasPhase1 = StripPhase(N1, State); // strip phase string from first name if needed
561 bool HasPhase2 = StripPhase(N2, State); // strip phase string from second name if needed
562 if (HasPhase1 && HasPhase2) // if both Names have a phase string, don't allow it.
563 throw ValueError("Phase can only be specified on one of the input key strings");
564
565 try {
566 // Get update pair
567 if (is_valid_parameter(N1, key1) && is_valid_parameter(N2, key2)) input_pair = generate_update_pair(key1, Prop1, key2, Prop2, v1, v2);
568 } catch (std::exception& e) {
569 // Input parameter parsing failed. Stop
570 throw ValueError(format("Input pair parsing failed for Name1: \"%s\", Name2: \"%s\"; err: %s", Name1.c_str(), Name2.c_str(), e.what()));
571 }
572
573 try {
574 output_parameters = output_parameter::get_output_parameters(Outputs);
575 } catch (std::exception& e) {
576 // Output parameter parsing failed. Stop.
577 throw ValueError(format("Output parameter parsing failed; error: %s", e.what()));
578 }
579
580 // Calculate the output(s). In the case of a failure, all values will be filled with _HUGE
581 _PropsSI_outputs(State, output_parameters, input_pair, v1, v2, IO);
582}
583
584std::vector<std::vector<double>> PropsSImulti(const std::vector<std::string>& Outputs, const std::string& Name1, const std::vector<double>& Prop1,
585 const std::string& Name2, const std::vector<double>& Prop2, const std::string& backend,
586 const std::vector<std::string>& fluids, const std::vector<double>& fractions) {
587 std::vector<std::vector<double>> IO;
588
589#if !defined(NO_ERROR_CATCHING)
590 try {
591#endif
592
593 // Call the subfunction that can bubble errors
594 _PropsSImulti(Outputs, Name1, Prop1, Name2, Prop2, backend, fluids, fractions, IO);
595
596 // Return the value(s)
597 return IO;
598
599#if !defined(NO_ERROR_CATCHING)
600 } catch (const std::exception& e) {
601 set_error_string(e.what());
602# if defined(PROPSSI_ERROR_STDOUT)
603 std::cout << e.what() << std::endl;
604# endif
605 if (get_debug_level() > 1) {
606 std::cout << e.what() << std::endl;
607 }
608 } catch (...) {
609 }
610#endif
611 return std::vector<std::vector<double>>();
612}
613double PropsSI(const std::string& Output, const std::string& Name1, double Prop1, const std::string& Name2, double Prop2, const std::string& Ref) {
614#if !defined(NO_ERROR_CATCHING)
615 try {
616#endif
617
618 // BEGIN OF TRY
619 // Here is the real code that is inside the try block
620
621 std::string backend, fluid;
622 extract_backend(Ref, backend, fluid);
623 std::vector<double> fractions(1, 1.0);
624 // extract_fractions checks for has_fractions_in_string / has_solution_concentration; no need to double check
625 std::string fluid_string = extract_fractions(fluid, fractions);
626 std::vector<std::vector<double>> IO;
627 _PropsSImulti(strsplit(Output, '&'), Name1, std::vector<double>(1, Prop1), Name2, std::vector<double>(1, Prop2), backend,
628 strsplit(fluid_string, '&'), fractions, IO);
629 if (IO.empty()) {
630 throw ValueError(get_global_param_string("errstring").c_str());
631 }
632 if (IO.size() != 1 || IO[0].size() != 1) {
633 throw ValueError(format("output should be 1x1; error was %s", get_global_param_string("errstring").c_str()));
634 }
635
636 double val = IO[0][0];
637
638 if (get_debug_level() > 1) {
639 std::cout << format("_PropsSI will return %g", val) << std::endl;
640 }
641 return val;
642 // END OF TRY
643#if !defined(NO_ERROR_CATCHING)
644 } catch (const std::exception& e) {
646 e.what()
647 + format(" : PropsSI(\"%s\",\"%s\",%0.10g,\"%s\",%0.10g,\"%s\")", Output.c_str(), Name1.c_str(), Prop1, Name2.c_str(), Prop2, Ref.c_str()));
648# if defined(PROPSSI_ERROR_STDOUT)
649 std::cout << e.what() << std::endl;
650# endif
651 if (get_debug_level() > 1) {
652 std::cout << e.what() << std::endl;
653 }
654 return _HUGE;
655 } catch (...) {
656 return _HUGE;
657 }
658#endif
659}
660
661bool add_fluids_as_JSON(const std::string& backend, const std::string& fluidstring) {
662 if (backend == "SRK" || backend == "PR") {
664 return true;
665 } else if (backend == "HEOS") {
666 JSONFluidLibrary::add_many(fluidstring);
667 return true;
668 } else if (backend == "PCSAFT") {
670 return true;
671 } else {
672 throw ValueError(format("You have provided an invalid backend [%s] to add_fluids_as_JSON; valid options are SRK, PR, HEOS", backend.c_str()));
673 }
674}
675
676#if defined(ENABLE_CATCH)
677TEST_CASE("Check inputs to PropsSI", "[PropsSI]") {
678 SECTION("Single state, single output") {
679 CHECK(ValidNumber(CoolProp::PropsSI("T", "P", 101325, "Q", 0, "Water")));
680 };
681 SECTION("Single state, single output, saturation derivative") {
682 CHECK(ValidNumber(CoolProp::PropsSI("d(P)/d(T)|sigma", "P", 101325, "Q", 0, "Water")));
683 };
684 SECTION("Single state, single output, pure incompressible") {
685 CHECK(ValidNumber(CoolProp::PropsSI("D", "P", 101325, "T", 300, "INCOMP::DowQ")));
686 };
687 SECTION("Single state, trivial output, pure incompressible") {
688 CHECK(ValidNumber(CoolProp::PropsSI("Tmin", "P", 0, "T", 0, "INCOMP::DowQ")));
689 };
690 SECTION("Bad input pair") {
691 CHECK(!ValidNumber(CoolProp::PropsSI("D", "Q", 0, "Q", 0, "Water")));
692 };
693 SECTION("Single state, single output, 40% incompressible") {
694 CHECK(ValidNumber(CoolProp::PropsSI("D", "P", 101325, "T", 300, "INCOMP::MEG[0.40]")));
695 };
696 SECTION("Single state, single output, predefined CoolProp mixture") {
697 CHECK(ValidNumber(CoolProp::PropsSI("T", "Q", 1, "P", 3e6, "HEOS::R125[0.7]&R32[0.3]")));
698 };
699 SECTION("Single state, single output") {
700 CHECK(ValidNumber(CoolProp::PropsSI("T", "P", 101325, "Q", 0, "HEOS::Water")));
701 };
702 SECTION("Single state, single output, predefined mixture") {
703 CHECK(ValidNumber(CoolProp::PropsSI("T", "P", 101325, "Q", 0, "R410A.mix")));
704 };
705 SECTION("Single state, single output, predefined mixture from REFPROP") {
706 CHECK(ValidNumber(CoolProp::PropsSI("T", "P", 101325, "Q", 0, "REFPROP::R410A.MIX")));
707 };
708 SECTION("Single state, single output, bad predefined mixture from REFPROP") {
709 CHECK(!ValidNumber(CoolProp::PropsSI("T", "P", 101325, "Q", 0, "REFPROP::RRRRRR.mix")));
710 };
711 SECTION("Predefined mixture") {
712 std::vector<double> p(1, 101325), Q(1, 1.0), z;
713 std::vector<std::string> outputs(1, "T");
714 outputs.push_back("Dmolar");
715 std::vector<std::vector<double>> IO;
716 std::vector<std::string> fluids(1, "R410A.mix");
717 CHECK_NOTHROW(IO = CoolProp::PropsSImulti(outputs, "P", p, "Q", Q, "HEOS", fluids, z));
718 };
719 SECTION("Single state, two outputs") {
720 std::vector<double> p(1, 101325), Q(1, 1.0), z(1, 1.0);
721 std::vector<std::string> outputs(1, "T");
722 outputs.push_back("Dmolar");
723 std::vector<std::string> fluids(1, "Water");
724 CHECK_NOTHROW(CoolProp::PropsSImulti(outputs, "P", p, "Q", Q, "HEOS", fluids, z));
725 };
726 SECTION("Single state, two bad outputs") {
727 std::vector<double> p(1, 101325), Q(1, 1.0), z(1, 1.0);
728 std::vector<std::vector<double>> IO;
729 std::vector<std::string> outputs(1, "???????");
730 outputs.push_back("?????????");
731 std::vector<std::string> fluids(1, "Water");
732 CHECK_NOTHROW(IO = CoolProp::PropsSImulti(outputs, "P", p, "Q", Q, "HEOS", fluids, z));
733 CHECK(IO.size() == 0);
734 };
735 SECTION("Two states, one output") {
736 std::vector<double> p(2, 101325), Q(2, 1.0), z(1, 1.0);
737 std::vector<std::string> outputs(1, "T");
738 std::vector<std::string> fluids(1, "Water");
739 CHECK_NOTHROW(CoolProp::PropsSImulti(outputs, "P", p, "Q", Q, "HEOS", fluids, z));
740 };
741 SECTION("Two states, two outputs") {
742 std::vector<double> p(2, 101325), Q(2, 1.0), z(1, 1.0);
743 std::vector<std::string> outputs(1, "T");
744 outputs.push_back("Dmolar");
745 std::vector<std::string> fluids(1, "Water");
746 CHECK_NOTHROW(CoolProp::PropsSImulti(outputs, "P", p, "Q", Q, "HEOS", fluids, z));
747 };
748 SECTION("cp and its derivative representation") {
749 std::vector<double> p(1, 101325), Q(1, 1.0), z(1, 1.0);
750 std::vector<std::vector<double>> IO;
751 std::vector<std::string> outputs(1, "Cpmolar");
752 outputs.push_back("d(Hmolar)/d(T)|P");
753 std::vector<std::string> fluids(1, "Water");
754 CHECK_NOTHROW(IO = CoolProp::PropsSImulti(outputs, "P", p, "Q", Q, "HEOS", fluids, z));
755 std::string errstring = get_global_param_string("errstring");
756 CAPTURE(errstring);
757 REQUIRE(!IO.empty());
758 CAPTURE(IO[0][0]);
759 CAPTURE(IO[0][1]);
760 CHECK(std::abs(IO[0][0] - IO[0][1]) < 1e-5);
761 };
762 SECTION("bad fluid") {
763 std::vector<double> p(1, 101325), Q(1, 1.0), z(1, 1.0);
764 std::vector<std::vector<double>> IO;
765 std::vector<std::string> outputs(1, "Cpmolar");
766 outputs.push_back("d(Hmolar)/d(T)|P");
767 std::vector<std::string> fluids(1, "????????");
768 CHECK_NOTHROW(IO = CoolProp::PropsSImulti(outputs, "P", p, "Q", Q, "HEOS", fluids, z));
769 std::string errstring = get_global_param_string("errstring");
770 CAPTURE(errstring);
771 REQUIRE(IO.empty());
772 };
773 SECTION("bad mole fraction length") {
774 std::vector<double> p(1, 101325), Q(1, 1.0), z(1, 1.0);
775 std::vector<std::vector<double>> IO;
776 std::vector<std::string> outputs(1, "T");
777 std::vector<std::string> fluids(1, "Water&Ethanol");
778 CHECK_NOTHROW(IO = CoolProp::PropsSImulti(outputs, "P", p, "Q", Q, "HEOS", fluids, z));
779 std::string errstring = get_global_param_string("errstring");
780 CAPTURE(errstring);
781 REQUIRE(IO.empty());
782 };
783 SECTION("bad input lengths") {
784 std::vector<double> p(1, 101325), Q(2, 1.0), z(100, 1.0);
785 std::vector<std::vector<double>> IO;
786 std::vector<std::string> outputs(1, "Cpmolar");
787 outputs.push_back("d(Hmolar)/d(T)|P");
788 std::vector<std::string> fluids(1, "Water");
789 CHECK_NOTHROW(IO = CoolProp::PropsSImulti(outputs, "P", p, "Q", Q, "HEOS", fluids, z));
790 std::string errstring = get_global_param_string("errstring");
791 CAPTURE(errstring);
792 REQUIRE(IO.empty());
793 };
794 SECTION("bad input pair") {
795 std::vector<double> Q(2, 1.0), z(1, 1.0);
796 std::vector<std::vector<double>> IO;
797 std::vector<std::string> outputs(1, "Cpmolar");
798 outputs.push_back("d(Hmolar)/d(T)|P");
799 std::vector<std::string> fluids(1, "Water");
800 CHECK_NOTHROW(IO = CoolProp::PropsSImulti(outputs, "Q", Q, "Q", Q, "HEOS", fluids, z));
801 std::string errstring = get_global_param_string("errstring");
802 CAPTURE(errstring);
803 REQUIRE(IO.empty());
804 };
805};
806#endif
807
808/****************************************************
809 * Props1SI *
810 ****************************************************/
811
812double Props1SI(std::string FluidName, std::string Output) {
813 bool valid_fluid1 = is_valid_fluid_string(FluidName);
814 bool valid_fluid2 = is_valid_fluid_string(Output);
815 if (valid_fluid1 && valid_fluid2) {
816 set_error_string(format("Both inputs to Props1SI [%s,%s] are valid fluids", Output.c_str(), FluidName.c_str()));
817 return _HUGE;
818 }
819 if (!valid_fluid1 && !valid_fluid2) {
820 set_error_string(format("Neither input to Props1SI [%s,%s] is a valid fluid", Output.c_str(), FluidName.c_str()));
821 return _HUGE;
822 }
823 if (!valid_fluid1 && valid_fluid2) {
824 // They are backwards, swap
825 std::swap(Output, FluidName);
826 }
827
828 // First input is the fluid, second input is the input parameter
829 double val1 = PropsSI(Output, "", 0, "", 0, FluidName);
830 if (!ValidNumber(val1)) {
831 set_error_string(format("Unable to use input parameter [%s] in Props1SI for fluid %s; error was %s", Output.c_str(), FluidName.c_str(),
832 get_global_param_string("errstring").c_str()));
833 return _HUGE;
834 } else {
835 return val1;
836 }
837}
838
839std::vector<std::vector<double>> Props1SImulti(const std::vector<std::string>& Outputs, const std::string& backend, const std::vector<std::string>& fluids, const std::vector<double>& fractions) {
840 std::vector<double> zero_vector(1, 0.);
841 std::vector<std::vector<double>> val1 = PropsSImulti(Outputs, "", zero_vector, "", zero_vector, backend, fluids, fractions);
842 // error handling is done in PropsSImulti, val1 will be an empty vector if an error occured
843 return val1;
844}
845
846
847#if defined(ENABLE_CATCH)
848TEST_CASE("Check inputs to Props1SI", "[Props1SI],[PropsSI]") {
849 SECTION("Good fluid, good parameter") {
850 CHECK(ValidNumber(CoolProp::Props1SI("Tcrit", "Water")));
851 };
852 SECTION("Good fluid, good parameter") {
853 CHECK(ValidNumber(CoolProp::PropsSI("Tcrit", "", 0, "", 0, "Water")));
854 };
855 SECTION("Good fluid, good parameter, inverted") {
856 CHECK(ValidNumber(CoolProp::Props1SI("Water", "Tcrit")));
857 };
858 SECTION("Good fluid, bad parameter") {
859 CHECK(!ValidNumber(CoolProp::Props1SI("Water", "????????????")));
860 };
861 SECTION("Bad fluid, good parameter") {
862 CHECK(!ValidNumber(CoolProp::Props1SI("?????", "Tcrit")));
863 };
864};
865#endif
866
867bool is_valid_fluid_string(const std::string& input_fluid_string) {
868 try {
869 std::string backend, fluid;
870 std::vector<double> fractions;
871 // First try to extract backend and fractions
872 extract_backend(input_fluid_string, backend, fluid);
873 std::string fluid_string = extract_fractions(fluid, fractions);
874 // We are going to let the factory function load the state
875 shared_ptr<AbstractState> State(AbstractState::factory(backend, fluid_string));
876 return true;
877 } catch (...) {
878 return false;
879 }
880}
881double saturation_ancillary(const std::string& fluid_name, const std::string& output, int Q, const std::string& input, double value) {
882
883 // Generate the state instance
884 std::vector<std::string> names(1, fluid_name);
886
887 parameters iInput = get_parameter_index(input);
888 parameters iOutput = get_parameter_index(output);
889
890 return HEOS.saturation_ancillary(iOutput, Q, iInput, value);
891}
892void set_reference_stateS(const std::string& fluid_string, const std::string& reference_state) {
893 std::string backend, fluid;
894 extract_backend(fluid_string, backend, fluid);
895 if (backend == "REFPROP") {
896
897 int ierr = 0, ixflag = 1;
898 double h0 = 0, s0 = 0, t0 = 0, p0 = 0;
899 char herr[255], hrf[4];
900 double x0[1] = {1};
901 const char* refstate = reference_state.c_str();
902 if (strlen(refstate) > 3) {
903 if (reference_state == "ASHRAE") {
904 strcpy(hrf, "ASH");
905 } else {
906 throw ValueError(format("Reference state string [%s] is more than 3 characters long", reference_state.c_str()));
907 }
908 } else {
909 strcpy(hrf, refstate);
910 }
911 REFPROP_SETREF(hrf, ixflag, x0, h0, s0, t0, p0, ierr, herr, 3, 255);
912 } else if (backend == "HEOS" || backend == "?") {
913 CoolProp::HelmholtzEOSMixtureBackend HEOS(std::vector<std::string>(1, fluid));
914 if (!reference_state.compare("IIR")) {
915 if (HEOS.Ttriple() > 273.15) {
916 throw ValueError(format("Cannot use IIR reference state; Ttriple [%Lg] is greater than 273.15 K", HEOS.Ttriple()));
917 }
918 HEOS.update(QT_INPUTS, 0, 273.15);
919
920 // Get current values for the enthalpy and entropy
921 double deltah = HEOS.hmass() - 200000; // offset from 200000 J/kg enthalpy
922 double deltas = HEOS.smass() - 1000; // offset from 1000 J/kg/K entropy
923 double delta_a1 = deltas / (HEOS.gas_constant() / HEOS.molar_mass());
924 double delta_a2 = -deltah / (HEOS.gas_constant() / HEOS.molar_mass() * HEOS.get_reducing_state().T);
925 // Change the value in the library for the given fluid
926 set_fluid_enthalpy_entropy_offset(fluid, delta_a1, delta_a2, "IIR");
927 if (get_debug_level() > 0) {
928 std::cout << format("set offsets to %0.15g and %0.15g\n", delta_a1, delta_a2);
929 }
930 } else if (!reference_state.compare("ASHRAE")) {
931 if (HEOS.Ttriple() > 233.15) {
932 throw ValueError(format("Cannot use ASHRAE reference state; Ttriple [%Lg] is greater than than 233.15 K", HEOS.Ttriple()));
933 }
934 HEOS.update(QT_INPUTS, 0, 233.15);
935
936 // Get current values for the enthalpy and entropy
937 double deltah = HEOS.hmass() - 0; // offset from 0 J/kg enthalpy
938 double deltas = HEOS.smass() - 0; // offset from 0 J/kg/K entropy
939 double delta_a1 = deltas / (HEOS.gas_constant() / HEOS.molar_mass());
940 double delta_a2 = -deltah / (HEOS.gas_constant() / HEOS.molar_mass() * HEOS.get_reducing_state().T);
941 // Change the value in the library for the given fluid
942 set_fluid_enthalpy_entropy_offset(fluid, delta_a1, delta_a2, "ASHRAE");
943 if (get_debug_level() > 0) {
944 std::cout << format("set offsets to %0.15g and %0.15g\n", delta_a1, delta_a2);
945 }
946 } else if (!reference_state.compare("NBP")) {
947 if (HEOS.p_triple() > 101325) {
948 throw ValueError(format("Cannot use NBP reference state; p_triple [%Lg Pa] is greater than than 101325 Pa", HEOS.p_triple()));
949 }
950 // Saturated liquid boiling point at 1 atmosphere
951 HEOS.update(PQ_INPUTS, 101325, 0);
952
953 double deltah = HEOS.hmass() - 0; // offset from 0 kJ/kg enthalpy
954 double deltas = HEOS.smass() - 0; // offset from 0 kJ/kg/K entropy
955 double delta_a1 = deltas / (HEOS.gas_constant() / HEOS.molar_mass());
956 double delta_a2 = -deltah / (HEOS.gas_constant() / HEOS.molar_mass() * HEOS.get_reducing_state().T);
957 // Change the value in the library for the given fluid
958 set_fluid_enthalpy_entropy_offset(fluid, delta_a1, delta_a2, "NBP");
959 if (get_debug_level() > 0) {
960 std::cout << format("set offsets to %0.15g and %0.15g\n", delta_a1, delta_a2);
961 }
962 } else if (!reference_state.compare("DEF")) {
963 set_fluid_enthalpy_entropy_offset(fluid, 0, 0, "DEF");
964 } else if (!reference_state.compare("RESET")) {
965 set_fluid_enthalpy_entropy_offset(fluid, 0, 0, "RESET");
966 } else {
967 throw ValueError(format("Reference state string is invalid: [%s]", reference_state.c_str()));
968 }
969 }
970}
971void set_reference_stateD(const std::string& Ref, double T, double rhomolar, double hmolar0, double smolar0) {
972 std::vector<std::string> _comps(1, Ref);
974
975 HEOS.update(DmolarT_INPUTS, rhomolar, T);
976
977 // Get current values for the enthalpy and entropy
978 double deltah = HEOS.hmolar() - hmolar0; // offset from specified enthalpy in J/mol
979 double deltas = HEOS.smolar() - smolar0; // offset from specified entropy in J/mol/K
980 double delta_a1 = deltas / (HEOS.gas_constant());
981 double delta_a2 = -deltah / (HEOS.gas_constant() * HEOS.get_reducing_state().T);
982 set_fluid_enthalpy_entropy_offset(Ref, delta_a1, delta_a2, "custom");
983}
984
985std::string get_global_param_string(const std::string& ParamName) {
986 if (!ParamName.compare("version")) {
987 return version;
988 } else if (!ParamName.compare("gitrevision")) {
989 return gitrevision;
990 } else if (!ParamName.compare("errstring")) {
991 std::string temp = error_string;
992 error_string = "";
993 return temp;
994 } else if (!ParamName.compare("warnstring")) {
995 std::string temp = warning_string;
996 warning_string = "";
997 return temp;
998 } else if (!ParamName.compare("FluidsList") || !ParamName.compare("fluids_list") || !ParamName.compare("fluidslist")) {
999 return get_fluid_list();
1000 } else if (!ParamName.compare("incompressible_list_pure")) {
1002 } else if (!ParamName.compare("incompressible_list_solution")) {
1004 } else if (!ParamName.compare("mixture_binary_pairs_list")) {
1006 } else if (!ParamName.compare("parameter_list")) {
1007 return get_csv_parameter_list();
1008 } else if (!ParamName.compare("predefined_mixtures")) {
1010 } else if (!ParamName.compare("HOME")) {
1011 return get_home_dir();
1012 } else if (ParamName == "REFPROP_version") {
1014 } else if (ParamName == "cubic_fluids_schema") {
1016 } else if (ParamName == "cubic_fluids_list") {
1018 } else if (ParamName == "pcsaft_fluids_schema") {
1020 } else {
1021 throw ValueError(format("Input parameter [%s] is invalid", ParamName.c_str()));
1022 }
1023};
1024#if defined(ENABLE_CATCH)
1025TEST_CASE("Check inputs to get_global_param_string", "[get_global_param_string]") {
1026 const int num_good_inputs = 8;
1027 std::string good_inputs[num_good_inputs] = {
1028 "version", "gitrevision", "fluids_list", "incompressible_list_pure", "incompressible_list_solution", "mixture_binary_pairs_list",
1029 "parameter_list", "predefined_mixtures"};
1030 std::ostringstream ss3c;
1031 for (int i = 0; i < num_good_inputs; ++i) {
1032 ss3c << "Test for" << good_inputs[i];
1033 SECTION(ss3c.str(), "") {
1034 CHECK_NOTHROW(CoolProp::get_global_param_string(good_inputs[i]));
1035 };
1036 }
1037 CHECK_THROWS(CoolProp::get_global_param_string(""));
1038};
1039#endif
1040std::string get_fluid_param_string(const std::string& FluidName, const std::string& ParamName) {
1041 std::string backend, fluid;
1042 extract_backend(FluidName, backend, fluid);
1043 shared_ptr<CoolProp::AbstractState> AS(CoolProp::AbstractState::factory(backend, fluid));
1044 return AS->fluid_param_string(ParamName);
1045}
1046#if defined(ENABLE_CATCH)
1047TEST_CASE("Check inputs to get_fluid_param_string", "[get_fluid_param_string]") {
1048 const int num_good_inputs = 10;
1049 std::string good_inputs[num_good_inputs] = {"aliases",
1050 "CAS",
1051 "ASHRAE34",
1052 "REFPROPName",
1053 "BibTeX-CONDUCTIVITY",
1054 "BibTeX-EOS",
1055 "BibTeX-CP0",
1056 "BibTeX-SURFACE_TENSION",
1057 "BibTeX-MELTING_LINE",
1058 "BibTeX-VISCOSITY"};
1059 std::ostringstream ss3c;
1060 for (int i = 0; i < num_good_inputs; ++i) {
1061 ss3c << "Test for" << good_inputs[i];
1062 SECTION(ss3c.str(), "") {
1063 CHECK_NOTHROW(CoolProp::get_fluid_param_string("Water", good_inputs[i]));
1064 };
1065 }
1066 CHECK_THROWS(CoolProp::get_fluid_param_string("", "aliases"));
1067 CHECK_THROWS(CoolProp::get_fluid_param_string("Water", ""));
1068 CHECK_THROWS(CoolProp::get_fluid_param_string("Water", "BibTeX-"));
1069 CHECK(CoolProp::get_fluid_param_string("Water", "pure") == "true");
1070 CHECK(CoolProp::get_fluid_param_string("R410A", "pure") == "false");
1071};
1072#endif
1073
1074std::string phase_lookup_string(phases Phase) {
1075 switch (Phase) {
1076 case iphase_liquid:
1077 return "liquid";
1079 return "supercritical";
1081 return "supercritical_gas";
1083 return "supercritical_liquid";
1085 return "critical_point";
1086 case iphase_gas:
1087 return "gas";
1088 case iphase_twophase:
1089 return "twophase";
1090 case iphase_unknown:
1091 return "unknown";
1092 case iphase_not_imposed:
1093 return "not_imposed";
1094 }
1095 throw ValueError("I should never be thrown");
1096}
1097std::string PhaseSI(const std::string& Name1, double Prop1, const std::string& Name2, double Prop2, const std::string& FluidName) {
1098 double Phase_double = PropsSI("Phase", Name1, Prop1, Name2, Prop2, FluidName); // Attempt to get "Phase" from PropsSI()
1099 if (!ValidNumber(Phase_double)) { // if the returned phase is invalid...
1100 std::string strPhase = phase_lookup_string(iphase_unknown); // phase is unknown.
1101 std::string strError = get_global_param_string("errstring").c_str(); // fetch waiting error string
1102 if (strError != "") { // if error string is not empty,
1103 strPhase.append(": " + strError); // append it to the phase string.
1104 }
1105 return strPhase; // return the "unknown" phase string
1106 } // else
1107 std::size_t Phase_int = static_cast<std::size_t>(Phase_double); // convert returned phase to int
1108 return phase_lookup_string(static_cast<phases>(Phase_int)); // return phase as a string
1109}
1110
1111/*
1112std::string PhaseSI(const std::string &Name1, double Prop1, const std::string &Name2, double Prop2, const std::string &FluidName, const std::vector<double> &z)
1113{
1114 double Phase_double = PropsSI("Phase",Name1,Prop1,Name2,Prop2,FluidName,z);
1115 if (!ValidNumber(Phase_double)){ return "";}
1116 std::size_t Phase_int = static_cast<std::size_t>(Phase_double);
1117 return phase_lookup_string(static_cast<phases>(Phase_int));
1118}
1119*/
1120} /* namespace CoolProp */