CoolProp 7.2.0
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 const 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 const 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 const 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 const std::size_t N1 = std::max(static_cast<std::size_t>(1), in1.size());
366 const 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 const 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 const bool HasPhase1 = StripPhase(N1, State); // strip phase string from first name if needed
561 const 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,
614 const std::string& FluidName) {
615#if !defined(NO_ERROR_CATCHING)
616 try {
617#endif
618
619 // BEGIN OF TRY
620 // Here is the real code that is inside the try block
621
622 std::string backend, fluid;
623 extract_backend(FluidName, backend, fluid);
624 std::vector<double> fractions(1, 1.0);
625 // extract_fractions checks for has_fractions_in_string / has_solution_concentration; no need to double check
626 const std::string fluid_string = extract_fractions(fluid, fractions);
627 std::vector<std::vector<double>> IO;
628 _PropsSImulti(strsplit(Output, '&'), Name1, std::vector<double>(1, Prop1), Name2, std::vector<double>(1, Prop2), backend,
629 strsplit(fluid_string, '&'), fractions, IO);
630 if (IO.empty()) {
631 throw ValueError(get_global_param_string("errstring").c_str());
632 }
633 if (IO.size() != 1 || IO[0].size() != 1) {
634 throw ValueError(format("output should be 1x1; error was %s", get_global_param_string("errstring").c_str()));
635 }
636
637 const double val = IO[0][0];
638
639 if (get_debug_level() > 1) {
640 std::cout << format("_PropsSI will return %g", val) << std::endl;
641 }
642 return val;
643 // END OF TRY
644#if !defined(NO_ERROR_CATCHING)
645 } catch (const std::exception& e) {
646 set_error_string(e.what()
647 + format(" : PropsSI(\"%s\",\"%s\",%0.10g,\"%s\",%0.10g,\"%s\")", Output.c_str(), Name1.c_str(), Prop1, Name2.c_str(), Prop2,
648 FluidName.c_str()));
649# if defined(PROPSSI_ERROR_STDOUT)
650 std::cout << e.what() << std::endl;
651# endif
652 if (get_debug_level() > 1) {
653 std::cout << e.what() << std::endl;
654 }
655 return _HUGE;
656 } catch (...) {
657 return _HUGE;
658 }
659#endif
660}
661
662bool add_fluids_as_JSON(const std::string& backend, const std::string& fluidstring) {
663 if (backend == "SRK" || backend == "PR") {
665 return true;
666 } else if (backend == "HEOS") {
667 JSONFluidLibrary::add_many(fluidstring);
668 return true;
669 } else if (backend == "PCSAFT") {
671 return true;
672 } else {
673 throw ValueError(format("You have provided an invalid backend [%s] to add_fluids_as_JSON; valid options are SRK, PR, HEOS", backend.c_str()));
674 }
675}
676
677#if defined(ENABLE_CATCH)
678TEST_CASE("Check inputs to PropsSI", "[PropsSI]") {
679 SECTION("Single state, single output") {
680 CHECK(ValidNumber(CoolProp::PropsSI("T", "P", 101325, "Q", 0, "Water")));
681 };
682 SECTION("Single state, single output, saturation derivative") {
683 CHECK(ValidNumber(CoolProp::PropsSI("d(P)/d(T)|sigma", "P", 101325, "Q", 0, "Water")));
684 };
685 SECTION("Single state, single output, pure incompressible") {
686 CHECK(ValidNumber(CoolProp::PropsSI("D", "P", 101325, "T", 300, "INCOMP::DowQ")));
687 };
688 SECTION("Single state, trivial output, pure incompressible") {
689 CHECK(ValidNumber(CoolProp::PropsSI("Tmin", "P", 0, "T", 0, "INCOMP::DowQ")));
690 };
691 SECTION("Bad input pair") {
692 CHECK(!ValidNumber(CoolProp::PropsSI("D", "Q", 0, "Q", 0, "Water")));
693 };
694 SECTION("Single state, single output, 40% incompressible") {
695 CHECK(ValidNumber(CoolProp::PropsSI("D", "P", 101325, "T", 300, "INCOMP::MEG[0.40]")));
696 };
697 SECTION("Single state, single output, predefined CoolProp mixture") {
698 CHECK(ValidNumber(CoolProp::PropsSI("T", "Q", 1, "P", 3e6, "HEOS::R125[0.7]&R32[0.3]")));
699 };
700 SECTION("Single state, single output") {
701 CHECK(ValidNumber(CoolProp::PropsSI("T", "P", 101325, "Q", 0, "HEOS::Water")));
702 };
703 SECTION("Single state, single output, predefined mixture") {
704 CHECK(ValidNumber(CoolProp::PropsSI("T", "P", 101325, "Q", 0, "R410A.mix")));
705 };
706 SECTION("Single state, single output, predefined mixture from REFPROP") {
707 CHECK(ValidNumber(CoolProp::PropsSI("T", "P", 101325, "Q", 0, "REFPROP::R410A.MIX")));
708 };
709 SECTION("Single state, single output, bad predefined mixture from REFPROP") {
710 CHECK(!ValidNumber(CoolProp::PropsSI("T", "P", 101325, "Q", 0, "REFPROP::RRRRRR.mix")));
711 };
712 SECTION("Predefined mixture") {
713 std::vector<double> p(1, 101325), Q(1, 1.0), z;
714 std::vector<std::string> outputs(1, "T");
715 outputs.push_back("Dmolar");
716 std::vector<std::vector<double>> IO;
717 std::vector<std::string> fluids(1, "R410A.mix");
718 CHECK_NOTHROW(IO = CoolProp::PropsSImulti(outputs, "P", p, "Q", Q, "HEOS", fluids, z));
719 };
720 SECTION("Single state, two outputs") {
721 std::vector<double> p(1, 101325), Q(1, 1.0), z(1, 1.0);
722 std::vector<std::string> outputs(1, "T");
723 outputs.push_back("Dmolar");
724 std::vector<std::string> fluids(1, "Water");
725 CHECK_NOTHROW(CoolProp::PropsSImulti(outputs, "P", p, "Q", Q, "HEOS", fluids, z));
726 };
727 SECTION("Single state, two bad outputs") {
728 std::vector<double> p(1, 101325), Q(1, 1.0), z(1, 1.0);
729 std::vector<std::vector<double>> IO;
730 std::vector<std::string> outputs(1, "???????");
731 outputs.push_back("?????????");
732 std::vector<std::string> fluids(1, "Water");
733 CHECK_NOTHROW(IO = CoolProp::PropsSImulti(outputs, "P", p, "Q", Q, "HEOS", fluids, z));
734 CHECK(IO.size() == 0);
735 };
736 SECTION("Two states, one output") {
737 std::vector<double> p(2, 101325), Q(2, 1.0), z(1, 1.0);
738 std::vector<std::string> outputs(1, "T");
739 std::vector<std::string> fluids(1, "Water");
740 CHECK_NOTHROW(CoolProp::PropsSImulti(outputs, "P", p, "Q", Q, "HEOS", fluids, z));
741 };
742 SECTION("Two states, two outputs") {
743 std::vector<double> p(2, 101325), Q(2, 1.0), z(1, 1.0);
744 std::vector<std::string> outputs(1, "T");
745 outputs.push_back("Dmolar");
746 std::vector<std::string> fluids(1, "Water");
747 CHECK_NOTHROW(CoolProp::PropsSImulti(outputs, "P", p, "Q", Q, "HEOS", fluids, z));
748 };
749 SECTION("cp and its derivative representation") {
750 std::vector<double> p(1, 101325), Q(1, 1.0), z(1, 1.0);
751 std::vector<std::vector<double>> IO;
752 std::vector<std::string> outputs(1, "Cpmolar");
753 outputs.push_back("d(Hmolar)/d(T)|P");
754 std::vector<std::string> fluids(1, "Water");
755 CHECK_NOTHROW(IO = CoolProp::PropsSImulti(outputs, "P", p, "Q", Q, "HEOS", fluids, z));
756 std::string errstring = get_global_param_string("errstring");
757 CAPTURE(errstring);
758 REQUIRE(!IO.empty());
759 CAPTURE(IO[0][0]);
760 CAPTURE(IO[0][1]);
761 CHECK(std::abs(IO[0][0] - IO[0][1]) < 1e-5);
762 };
763 SECTION("bad fluid") {
764 std::vector<double> p(1, 101325), Q(1, 1.0), z(1, 1.0);
765 std::vector<std::vector<double>> IO;
766 std::vector<std::string> outputs(1, "Cpmolar");
767 outputs.push_back("d(Hmolar)/d(T)|P");
768 std::vector<std::string> fluids(1, "????????");
769 CHECK_NOTHROW(IO = CoolProp::PropsSImulti(outputs, "P", p, "Q", Q, "HEOS", fluids, z));
770 std::string errstring = get_global_param_string("errstring");
771 CAPTURE(errstring);
772 REQUIRE(IO.empty());
773 };
774 SECTION("bad mole fraction length") {
775 std::vector<double> p(1, 101325), Q(1, 1.0), z(1, 1.0);
776 std::vector<std::vector<double>> IO;
777 std::vector<std::string> outputs(1, "T");
778 std::vector<std::string> fluids(1, "Water&Ethanol");
779 CHECK_NOTHROW(IO = CoolProp::PropsSImulti(outputs, "P", p, "Q", Q, "HEOS", fluids, z));
780 std::string errstring = get_global_param_string("errstring");
781 CAPTURE(errstring);
782 REQUIRE(IO.empty());
783 };
784 SECTION("bad input lengths") {
785 std::vector<double> p(1, 101325), Q(2, 1.0), z(100, 1.0);
786 std::vector<std::vector<double>> IO;
787 std::vector<std::string> outputs(1, "Cpmolar");
788 outputs.push_back("d(Hmolar)/d(T)|P");
789 std::vector<std::string> fluids(1, "Water");
790 CHECK_NOTHROW(IO = CoolProp::PropsSImulti(outputs, "P", p, "Q", Q, "HEOS", fluids, z));
791 std::string errstring = get_global_param_string("errstring");
792 CAPTURE(errstring);
793 REQUIRE(IO.empty());
794 };
795 SECTION("bad input pair") {
796 std::vector<double> Q(2, 1.0), z(1, 1.0);
797 std::vector<std::vector<double>> IO;
798 std::vector<std::string> outputs(1, "Cpmolar");
799 outputs.push_back("d(Hmolar)/d(T)|P");
800 std::vector<std::string> fluids(1, "Water");
801 CHECK_NOTHROW(IO = CoolProp::PropsSImulti(outputs, "Q", Q, "Q", Q, "HEOS", fluids, z));
802 std::string errstring = get_global_param_string("errstring");
803 CAPTURE(errstring);
804 REQUIRE(IO.empty());
805 };
806};
807#endif
808
809/****************************************************
810 * Props1SI *
811 ****************************************************/
812
813double Props1SI(std::string FluidName, std::string Output) {
814 const bool valid_fluid1 = is_valid_fluid_string(FluidName);
815 const bool valid_fluid2 = is_valid_fluid_string(Output);
816 if (valid_fluid1 && valid_fluid2) {
817 set_error_string(format("Both inputs to Props1SI [%s,%s] are valid fluids", Output.c_str(), FluidName.c_str()));
818 return _HUGE;
819 }
820 if (!valid_fluid1 && !valid_fluid2) {
821 set_error_string(format("Neither input to Props1SI [%s,%s] is a valid fluid", Output.c_str(), FluidName.c_str()));
822 return _HUGE;
823 }
824 if (!valid_fluid1 && valid_fluid2) {
825 // They are backwards, swap
826 std::swap(Output, FluidName);
827 }
828
829 // First input is the fluid, second input is the input parameter
830 const double val1 = PropsSI(Output, "", 0, "", 0, FluidName);
831 if (!ValidNumber(val1)) {
832 set_error_string(format("Unable to use input parameter [%s] in Props1SI for fluid %s; error was %s", Output.c_str(), FluidName.c_str(),
833 get_global_param_string("errstring").c_str()));
834 return _HUGE;
835 } else {
836 return val1;
837 }
838}
839
840std::vector<std::vector<double>> Props1SImulti(const std::vector<std::string>& Outputs, const std::string& backend,
841 const std::vector<std::string>& fluids, const std::vector<double>& fractions) {
842 std::vector<double> zero_vector(1, 0.);
843 std::vector<std::vector<double>> val1 = PropsSImulti(Outputs, "", zero_vector, "", zero_vector, backend, fluids, fractions);
844 // error handling is done in PropsSImulti, val1 will be an empty vector if an error occured
845 return val1;
846}
847
848#if defined(ENABLE_CATCH)
849TEST_CASE("Check inputs to Props1SI", "[Props1SI],[PropsSI]") {
850 SECTION("Good fluid, good parameter") {
851 CHECK(ValidNumber(CoolProp::Props1SI("Tcrit", "Water")));
852 };
853 SECTION("Good fluid, good parameter") {
854 CHECK(ValidNumber(CoolProp::PropsSI("Tcrit", "", 0, "", 0, "Water")));
855 };
856 SECTION("Good fluid, good parameter, inverted") {
857 CHECK(ValidNumber(CoolProp::Props1SI("Water", "Tcrit")));
858 };
859 SECTION("Good fluid, bad parameter") {
860 CHECK(!ValidNumber(CoolProp::Props1SI("Water", "????????????")));
861 };
862 SECTION("Bad fluid, good parameter") {
863 CHECK(!ValidNumber(CoolProp::Props1SI("?????", "Tcrit")));
864 };
865};
866#endif
867
868bool is_valid_fluid_string(const std::string& fluidstring) {
869 try {
870 std::string backend, fluid;
871 std::vector<double> fractions;
872 // First try to extract backend and fractions
873 extract_backend(fluidstring, backend, fluid);
874 std::string fluid_string = extract_fractions(fluid, fractions);
875 // We are going to let the factory function load the state
876 shared_ptr<AbstractState> State(AbstractState::factory(backend, fluid_string));
877 return true;
878 } catch (...) {
879 return false;
880 }
881}
882double saturation_ancillary(const std::string& fluid_name, const std::string& output, int Q, const std::string& input, double value) {
883
884 // Generate the state instance
885 std::vector<std::string> names(1, fluid_name);
887
888 parameters iInput = get_parameter_index(input);
889 parameters iOutput = get_parameter_index(output);
890
891 return HEOS.saturation_ancillary(iOutput, Q, iInput, value);
892}
893void set_reference_stateS(const std::string& FluidName, const std::string& reference_state) {
894 std::string backend, fluid;
895 extract_backend(FluidName, backend, fluid);
896 if (backend == "REFPROP") {
897
898 int ierr = 0, ixflag = 1;
899 double h0 = 0, s0 = 0, t0 = 0, p0 = 0;
900 char herr[255], hrf[4];
901 double x0[1] = {1};
902 const char* refstate = reference_state.c_str();
903 if (strlen(refstate) > 3) {
904 if (reference_state == "ASHRAE") {
905 strncpy(hrf, "ASH", sizeof(hrf) - 1);
906 hrf[sizeof(hrf) - 1] = '\0';
907 } else {
908 throw ValueError(format("Reference state string [%s] is more than 3 characters long", reference_state.c_str()));
909 }
910 } else {
911 strncpy(hrf, refstate, sizeof(hrf) - 1);
912 hrf[sizeof(hrf) - 1] = '\0';
913 }
914 REFPROP_SETREF(hrf, ixflag, x0, h0, s0, t0, p0, ierr, herr, 3, 255);
915 } else if (backend == "HEOS" || backend == "?") {
916 CoolProp::HelmholtzEOSMixtureBackend HEOS(std::vector<std::string>(1, fluid));
917 if (reference_state == "IIR") {
918 if (HEOS.Ttriple() > 273.15) {
919 throw ValueError(format("Cannot use IIR reference state; Ttriple [%Lg] is greater than 273.15 K", HEOS.Ttriple()));
920 }
921 HEOS.update(QT_INPUTS, 0, 273.15);
922
923 // Get current values for the enthalpy and entropy
924 double deltah = HEOS.hmass() - 200000; // offset from 200000 J/kg enthalpy
925 double deltas = HEOS.smass() - 1000; // offset from 1000 J/kg/K entropy
926 double delta_a1 = deltas / (HEOS.gas_constant() / HEOS.molar_mass());
927 double delta_a2 = -deltah / (HEOS.gas_constant() / HEOS.molar_mass() * HEOS.get_reducing_state().T);
928 // Change the value in the library for the given fluid
929 set_fluid_enthalpy_entropy_offset(fluid, delta_a1, delta_a2, "IIR");
930 if (get_debug_level() > 0) {
931 std::cout << format("set offsets to %0.15g and %0.15g\n", delta_a1, delta_a2);
932 }
933 } else if (reference_state == "ASHRAE") {
934 if (HEOS.Ttriple() > 233.15) {
935 throw ValueError(format("Cannot use ASHRAE reference state; Ttriple [%Lg] is greater than than 233.15 K", HEOS.Ttriple()));
936 }
937 HEOS.update(QT_INPUTS, 0, 233.15);
938
939 // Get current values for the enthalpy and entropy
940 double deltah = HEOS.hmass() - 0; // offset from 0 J/kg enthalpy
941 double deltas = HEOS.smass() - 0; // offset from 0 J/kg/K entropy
942 double delta_a1 = deltas / (HEOS.gas_constant() / HEOS.molar_mass());
943 double delta_a2 = -deltah / (HEOS.gas_constant() / HEOS.molar_mass() * HEOS.get_reducing_state().T);
944 // Change the value in the library for the given fluid
945 set_fluid_enthalpy_entropy_offset(fluid, delta_a1, delta_a2, "ASHRAE");
946 if (get_debug_level() > 0) {
947 std::cout << format("set offsets to %0.15g and %0.15g\n", delta_a1, delta_a2);
948 }
949 } else if (reference_state == "NBP") {
950 if (HEOS.p_triple() > 101325) {
951 throw ValueError(format("Cannot use NBP reference state; p_triple [%Lg Pa] is greater than than 101325 Pa", HEOS.p_triple()));
952 }
953 // Saturated liquid boiling point at 1 atmosphere
954 HEOS.update(PQ_INPUTS, 101325, 0);
955
956 double deltah = HEOS.hmass() - 0; // offset from 0 kJ/kg enthalpy
957 double deltas = HEOS.smass() - 0; // offset from 0 kJ/kg/K entropy
958 double delta_a1 = deltas / (HEOS.gas_constant() / HEOS.molar_mass());
959 double delta_a2 = -deltah / (HEOS.gas_constant() / HEOS.molar_mass() * HEOS.get_reducing_state().T);
960 // Change the value in the library for the given fluid
961 set_fluid_enthalpy_entropy_offset(fluid, delta_a1, delta_a2, "NBP");
962 if (get_debug_level() > 0) {
963 std::cout << format("set offsets to %0.15g and %0.15g\n", delta_a1, delta_a2);
964 }
965 } else if (reference_state == "DEF") {
966 set_fluid_enthalpy_entropy_offset(fluid, 0, 0, "DEF");
967 } else if (reference_state == "RESET") {
968 set_fluid_enthalpy_entropy_offset(fluid, 0, 0, "RESET");
969 } else {
970 throw ValueError(format("Reference state string is invalid: [%s]", reference_state.c_str()));
971 }
972 }
973}
974void set_reference_stateD(const std::string& FluidName, double T, double rhomolar, double hmolar0, double smolar0) {
975 std::vector<std::string> _comps(1, FluidName);
977
978 HEOS.update(DmolarT_INPUTS, rhomolar, T);
979
980 // Get current values for the enthalpy and entropy
981 double deltah = HEOS.hmolar() - hmolar0; // offset from specified enthalpy in J/mol
982 double deltas = HEOS.smolar() - smolar0; // offset from specified entropy in J/mol/K
983 double delta_a1 = deltas / (HEOS.gas_constant());
984 double delta_a2 = -deltah / (HEOS.gas_constant() * HEOS.get_reducing_state().T);
985 set_fluid_enthalpy_entropy_offset(FluidName, delta_a1, delta_a2, "custom");
986}
987
988std::string get_global_param_string(const std::string& ParamName) {
989 if (ParamName == "version") {
990 return version;
991 } else if (ParamName == "gitrevision") {
992 return gitrevision;
993 } else if (ParamName == "errstring") {
994 std::string temp = error_string;
995 error_string = "";
996 return temp;
997 } else if (ParamName == "warnstring") {
998 std::string temp = warning_string;
999 warning_string = "";
1000 return temp;
1001 } else if (ParamName == "FluidsList" || ParamName == "fluids_list" || ParamName == "fluidslist") {
1002 return get_fluid_list();
1003 } else if (ParamName == "incompressible_list_pure") {
1005 } else if (ParamName == "incompressible_list_solution") {
1007 } else if (ParamName == "mixture_binary_pairs_list") {
1009 } else if (ParamName == "parameter_list") {
1010 return get_csv_parameter_list();
1011 } else if (ParamName == "predefined_mixtures") {
1013 } else if (ParamName == "HOME") {
1014 return get_home_dir();
1015 } else if (ParamName == "REFPROP_version") {
1017 } else if (ParamName == "cubic_fluids_schema") {
1019 } else if (ParamName == "cubic_fluids_list") {
1021 } else if (ParamName == "pcsaft_fluids_schema") {
1023 } else {
1024 throw ValueError(format("Input parameter [%s] is invalid", ParamName.c_str()));
1025 }
1026};
1027#if defined(ENABLE_CATCH)
1028TEST_CASE("Check inputs to get_global_param_string", "[get_global_param_string]") {
1029 const int num_good_inputs = 8;
1030 std::string good_inputs[num_good_inputs] = {
1031 "version", "gitrevision", "fluids_list", "incompressible_list_pure", "incompressible_list_solution", "mixture_binary_pairs_list",
1032 "parameter_list", "predefined_mixtures"};
1033 std::ostringstream ss3c;
1034 for (int i = 0; i < num_good_inputs; ++i) {
1035 ss3c << "Test for" << good_inputs[i];
1036 SECTION(ss3c.str(), "") {
1037 CHECK_NOTHROW(CoolProp::get_global_param_string(good_inputs[i]));
1038 };
1039 }
1040 CHECK_THROWS(CoolProp::get_global_param_string(""));
1041};
1042#endif
1043std::string get_fluid_param_string(const std::string& FluidName, const std::string& ParamName) {
1044 std::string backend, fluid;
1045 extract_backend(FluidName, backend, fluid);
1046 shared_ptr<CoolProp::AbstractState> AS(CoolProp::AbstractState::factory(backend, fluid));
1047 return AS->fluid_param_string(ParamName);
1048}
1049#if defined(ENABLE_CATCH)
1050TEST_CASE("Check inputs to get_fluid_param_string", "[get_fluid_param_string]") {
1051 const int num_good_inputs = 10;
1052 std::string good_inputs[num_good_inputs] = {"aliases",
1053 "CAS",
1054 "ASHRAE34",
1055 "REFPROPName",
1056 "BibTeX-CONDUCTIVITY",
1057 "BibTeX-EOS",
1058 "BibTeX-CP0",
1059 "BibTeX-SURFACE_TENSION",
1060 "BibTeX-MELTING_LINE",
1061 "BibTeX-VISCOSITY"};
1062 std::ostringstream ss3c;
1063 for (int i = 0; i < num_good_inputs; ++i) {
1064 ss3c << "Test for" << good_inputs[i];
1065 SECTION(ss3c.str(), "") {
1066 CHECK_NOTHROW(CoolProp::get_fluid_param_string("Water", good_inputs[i]));
1067 };
1068 }
1069 CHECK_THROWS(CoolProp::get_fluid_param_string("", "aliases"));
1070 CHECK_THROWS(CoolProp::get_fluid_param_string("Water", ""));
1071 CHECK_THROWS(CoolProp::get_fluid_param_string("Water", "BibTeX-"));
1072 CHECK(CoolProp::get_fluid_param_string("Water", "pure") == "true");
1073 CHECK(CoolProp::get_fluid_param_string("R410A", "pure") == "false");
1074};
1075#endif
1076
1077std::string phase_lookup_string(phases Phase) {
1078 switch (Phase) {
1079 case iphase_liquid:
1080 return "liquid";
1082 return "supercritical";
1084 return "supercritical_gas";
1086 return "supercritical_liquid";
1088 return "critical_point";
1089 case iphase_gas:
1090 return "gas";
1091 case iphase_twophase:
1092 return "twophase";
1093 case iphase_unknown:
1094 return "unknown";
1095 case iphase_not_imposed:
1096 return "not_imposed";
1097 }
1098 throw ValueError("I should never be thrown");
1099}
1100std::string PhaseSI(const std::string& Name1, double Prop1, const std::string& Name2, double Prop2, const std::string& FluidName) {
1101 double Phase_double = PropsSI("Phase", Name1, Prop1, Name2, Prop2, FluidName); // Attempt to get "Phase" from PropsSI()
1102 if (!ValidNumber(Phase_double)) { // if the returned phase is invalid...
1103 std::string strPhase = phase_lookup_string(iphase_unknown); // phase is unknown.
1104 std::string strError = get_global_param_string("errstring").c_str(); // fetch waiting error string
1105 if (strError != "") { // if error string is not empty,
1106 strPhase.append(": " + strError); // append it to the phase string.
1107 }
1108 return strPhase; // return the "unknown" phase string
1109 } // else
1110 std::size_t Phase_int = static_cast<std::size_t>(Phase_double); // convert returned phase to int
1111 return phase_lookup_string(static_cast<phases>(Phase_int)); // return phase as a string
1112}
1113
1114/*
1115std::string PhaseSI(const std::string &Name1, double Prop1, const std::string &Name2, double Prop2, const std::string &FluidName, const std::vector<double> &z)
1116{
1117 double Phase_double = PropsSI("Phase",Name1,Prop1,Name2,Prop2,FluidName,z);
1118 if (!ValidNumber(Phase_double)){ return "";}
1119 std::size_t Phase_int = static_cast<std::size_t>(Phase_double);
1120 return phase_lookup_string(static_cast<phases>(Phase_int));
1121}
1122*/
1123} /* namespace CoolProp */