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