#if defined(_MSC_VER) # ifndef _CRTDBG_MAP_ALLOC # define _CRTDBG_MAP_ALLOC # endif # ifndef _CRT_SECURE_NO_WARNINGS # define _CRT_SECURE_NO_WARNINGS # endif # include #endif #include "CoolProp.h" #include "AbstractState.h" #if defined(__ISWINDOWS__) # include # ifdef min # undef min # endif # ifdef max # undef max # endif #else # ifndef DBL_EPSILON # include # define DBL_EPSILON std::numeric_limits::epsilon() # endif #endif #include #include #include #include #include #include #include #include #include "CoolPropTools.h" #include "Solvers.h" #include "MatrixMath.h" #include "Backends/Helmholtz/Fluids/FluidLibrary.h" #include "Backends/Incompressible/IncompressibleLibrary.h" #include "Backends/Incompressible/IncompressibleBackend.h" #include "Backends/Helmholtz/HelmholtzEOSBackend.h" #include "Backends/Helmholtz/MixtureParameters.h" #include "DataStructures.h" #include "Backends/REFPROP/REFPROPMixtureBackend.h" #include "Backends/Cubics/CubicsLibrary.h" #include "Backends/PCSAFT/PCSAFTLibrary.h" #if defined(ENABLE_CATCH) # include #endif namespace CoolProp { static int debug_level = 0; static std::string error_string; static std::string warning_string; void set_debug_level(int level) { debug_level = level; } int get_debug_level(void) { return debug_level; } //// This is very hacky, but pull the git revision from the file #include "gitrevision.h" // Contents are like "std::string gitrevision = "aa121435436ggregrea4t43t433";" #include "cpversion.h" // Contents are like "char version [] = "2.5";" void set_warning_string(const std::string& warning) { warning_string = warning; } void set_error_string(const std::string& error) { error_string = error; } // Return true if the string has "BACKEND::*" format where * signifies a wildcard bool has_backend_in_string(const std::string& fluid_string, std::size_t& i) { i = fluid_string.find("::"); return i != std::string::npos; } void extract_backend(std::string fluid_string, std::string& backend, std::string& fluid) { std::size_t i; // For backwards compatibility reasons, if "REFPROP-" or "REFPROP-MIX:" start // the fluid_string, replace them with "REFPROP::" if (fluid_string.find("REFPROP-MIX:") == 0) { fluid_string.replace(0, 12, "REFPROP::"); } if (fluid_string.find("REFPROP-") == 0) { fluid_string.replace(0, 8, "REFPROP::"); } if (has_backend_in_string(fluid_string, i)) { // Part without the :: backend = fluid_string.substr(0, i); // Fluid name after the :: fluid = fluid_string.substr(i + 2); } else { backend = "?"; fluid = fluid_string; } if (get_debug_level() > 10) std::cout << format("%s:%d: backend extracted. backend: %s. fluid: %s\n", __FILE__, __LINE__, backend.c_str(), fluid.c_str()); } bool has_fractions_in_string(const std::string& fluid_string) { // If can find both "[" and "]", it must have mole fractions encoded as string return (fluid_string.find("[") != std::string::npos && fluid_string.find("]") != std::string::npos); } bool has_solution_concentration(const std::string& fluid_string) { // If can find "-", expect mass fractions encoded as string return (fluid_string.find('-') != std::string::npos && fluid_string.find('%') != std::string::npos); } struct delim : std::numpunct { char m_c; delim(char c) : m_c(c) {}; char do_decimal_point() const { return m_c; } }; std::string extract_fractions(const std::string& fluid_string, std::vector& fractions) { if (has_fractions_in_string(fluid_string)) { fractions.clear(); std::vector names; // Break up into pairs - like "Ethane[0.5]&Methane[0.5]" -> ("Ethane[0.5]","Methane[0.5]") std::vector pairs = strsplit(fluid_string, '&'); for (std::size_t i = 0; i < pairs.size(); ++i) { const std::string& fluid = pairs[i]; // Must end with ']' if (fluid[fluid.size() - 1] != ']') throw ValueError(format("Fluid entry [%s] must end with ']' character", pairs[i].c_str())); // Split at '[', but first remove the ']' from the end by taking a substring std::vector name_fraction = strsplit(fluid.substr(0, fluid.size() - 1), '['); if (name_fraction.size() != 2) { throw ValueError(format("Could not break [%s] into name/fraction", fluid.substr(0, fluid.size() - 1).c_str())); } // Convert fraction to a double const std::string &name = name_fraction[0], &fraction = name_fraction[1]; // The default locale for conversion from string to double is en_US with . as the deliminter // Good: 0.1234 Bad: 0,1234 // But you can change the punctuation character for fraction parsing // with the configuration variable FLOAT_PUNCTUATION to change the locale to something more convenient for you (e.g., a ',') // See also http://en.cppreference.com/w/cpp/locale/numpunct/decimal_point std::stringstream ssfraction(fraction); const char c = get_config_string(FLOAT_PUNCTUATION)[0]; ssfraction.imbue(std::locale(ssfraction.getloc(), new delim(c))); double f; ssfraction >> f; if (ssfraction.rdbuf()->in_avail() != 0) { throw ValueError(format("fraction [%s] was not converted fully", fraction.c_str())); } if (f > 1 || f < 0) { throw ValueError(format("fraction [%s] was not converted to a value between 0 and 1 inclusive", fraction.c_str())); } if ((f > 10 * DBL_EPSILON) || // Only push component if fraction is positive and non-zero (pairs.size() == 1)) // ..or if there is only one fluid (i.e. INCOMP backend ) { // And add to vector fractions.push_back(f); // Add name names.push_back(name); } } if (get_debug_level() > 10) std::cout << format("%s:%d: Detected fractions of %s for %s.", __FILE__, __LINE__, vec_to_string(fractions).c_str(), (strjoin(names, "&")).c_str()); // Join fluids back together return strjoin(names, "&"); } else if (has_solution_concentration(fluid_string)) { fractions.clear(); double x; std::vector fluid_parts = strsplit(fluid_string, '-'); // Check it worked if (fluid_parts.size() != 2) { throw ValueError( format("Format of incompressible solution string [%s] is invalid, should be like \"EG-20%\" or \"EG-0.2\" ", fluid_string.c_str())); } // Convert the concentration into a string char* pEnd; x = strtod(fluid_parts[1].c_str(), &pEnd); // Check if per cent or fraction syntax is used if (!strcmp(pEnd, "%")) { x *= 0.01; } fractions.push_back(x); if (get_debug_level() > 10) std::cout << format("%s:%d: Detected incompressible concentration of %s for %s.", __FILE__, __LINE__, vec_to_string(fractions).c_str(), fluid_parts[0].c_str()); return fluid_parts[0]; } else { return fluid_string; } } void _PropsSI_initialize(const std::string& backend, const std::vector& fluid_names, const std::vector& z, shared_ptr& State) { if (fluid_names.empty()) { throw ValueError("fluid_names cannot be empty"); } std::vector fractions(1, 1.0); // Default to one component, unity fraction const std::vector* fractions_ptr = nullptr; // Pointer to the array to be used; if (fluid_names.size() > 1) { // Set the pointer - we are going to use the supplied fractions; they must be provided fractions_ptr = &z; // Reset the state State.reset(AbstractState::factory(backend, fluid_names)); } else if (fluid_names.size() == 1) { if (has_fractions_in_string(fluid_names[0]) || has_solution_concentration(fluid_names[0])) { // Extract fractions from the string const std::string fluid_string = extract_fractions(fluid_names[0], fractions); // Set the pointer - we are going to use the extracted fractions fractions_ptr = &fractions; // Reset the state State.reset(AbstractState::factory(backend, fluid_string)); } else { if (z.empty()) { // Set the pointer - we are going to use the default fractions fractions_ptr = &fractions; } else { // Set the pointer - we are going to use the provided fractions fractions_ptr = &z; } // Reset the state State.reset(AbstractState::factory(backend, fluid_names)); } } else { // The only path where fractions_ptr stays nullptr throw ValueError("fractions_ptr is nullptr"); } if (!State->available_in_high_level()) { throw ValueError( "This AbstractState derived class cannot be used in the high-level interface; see www.coolprop.org/dev/coolprop/LowLevelAPI.html"); } // Set the fraction for the state if (State->using_mole_fractions()) { // If a predefined mixture or a pure fluid, the fractions will already be set if (State->get_mole_fractions().empty()) { State->set_mole_fractions(*fractions_ptr); } } else if (State->using_mass_fractions()) { State->set_mass_fractions(*fractions_ptr); } else if (State->using_volu_fractions()) { State->set_volu_fractions(*fractions_ptr); } else { if (get_debug_level() > 50) std::cout << format("%s:%d: _PropsSI, could not set composition to %s, defaulting to mole fraction.\n", __FILE__, __LINE__, vec_to_string(z).c_str()) .c_str(); } } struct output_parameter { enum OutputParametersType { OUTPUT_TYPE_UNSET = 0, OUTPUT_TYPE_TRIVIAL, OUTPUT_TYPE_NORMAL, OUTPUT_TYPE_FIRST_DERIVATIVE, OUTPUT_TYPE_FIRST_SATURATION_DERIVATIVE, OUTPUT_TYPE_SECOND_DERIVATIVE }; CoolProp::parameters Of1, Wrt1, Constant1, Wrt2, Constant2; OutputParametersType type; /// Parse a '&' separated string into a data structure with one entry per output /// Covers both normal and derivative outputs static std::vector get_output_parameters(const std::vector& Outputs) { std::vector outputs; for (std::vector::const_iterator str = Outputs.begin(); str != Outputs.end(); ++str) { output_parameter out; CoolProp::parameters iOutput; if (is_valid_parameter(*str, iOutput)) { out.Of1 = iOutput; if (is_trivial_parameter(iOutput)) { out.type = OUTPUT_TYPE_TRIVIAL; } else { out.type = OUTPUT_TYPE_NORMAL; } } else if (is_valid_first_saturation_derivative(*str, out.Of1, out.Wrt1)) { out.type = OUTPUT_TYPE_FIRST_SATURATION_DERIVATIVE; } else if (is_valid_first_derivative(*str, out.Of1, out.Wrt1, out.Constant1)) { out.type = OUTPUT_TYPE_FIRST_DERIVATIVE; } else if (is_valid_second_derivative(*str, out.Of1, out.Wrt1, out.Constant1, out.Wrt2, out.Constant2)) { out.type = OUTPUT_TYPE_SECOND_DERIVATIVE; } else { throw ValueError(format("Output string is invalid [%s]", str->c_str())); } outputs.push_back(out); } return outputs; }; }; void _PropsSI_outputs(shared_ptr& State, const std::vector& output_parameters, CoolProp::input_pairs input_pair, const std::vector& in1, const std::vector& in2, std::vector>& IO) { // Check the inputs if (in1.size() != in2.size()) { throw ValueError(format("lengths of in1 [%d] and in2 [%d] are not the same", in1.size(), in2.size())); } const bool one_input_one_output = (in1.size() == 1 && in2.size() == 1 && output_parameters.size() == 1); // If all trivial outputs, never do a state update bool all_trivial_outputs = true; for (std::size_t j = 0; j < output_parameters.size(); ++j) { if (output_parameters[j].type != output_parameter::OUTPUT_TYPE_TRIVIAL) { all_trivial_outputs = false; } } parameters p1, p2; // If all outputs are also inputs, never do a state update bool all_outputs_in_inputs = true; if (input_pair != INPUT_PAIR_INVALID) { // Split the input pair into parameters split_input_pair(input_pair, p1, p2); // See if each parameter is in the output vector and is a normal type input for (std::size_t j = 0; j < output_parameters.size(); ++j) { if (output_parameters[j].type != output_parameter::OUTPUT_TYPE_NORMAL) { all_outputs_in_inputs = false; break; } if (!(output_parameters[j].Of1 == p1 || output_parameters[j].Of1 == p2)) { all_outputs_in_inputs = false; break; } } } else { if (!all_trivial_outputs) { throw ValueError(format("Input pair variable is invalid and output(s) are non-trivial; cannot do state update")); } all_outputs_in_inputs = false; } if (get_debug_level() > 100) { std::cout << format("%s (%d): input pair = %d ", __FILE__, __LINE__, input_pair) << std::endl; std::cout << format("%s (%d): in1 = %s ", __FILE__, __LINE__, vec_to_string(in1).c_str()) << std::endl; std::cout << format("%s (%d): in2 = %s ", __FILE__, __LINE__, vec_to_string(in2).c_str()) << std::endl; } // Get configuration variable for line tracing, see #1443 const bool use_guesses = get_config_bool(USE_GUESSES_IN_PROPSSI); GuessesStructure guesses; // Resize the output matrix const std::size_t N1 = std::max(static_cast(1), in1.size()); const std::size_t N2 = std::max(static_cast(1), output_parameters.size()); IO.resize(N1, std::vector(N2, _HUGE)); // Throw an error if at the end, there were no successes bool success = false; bool success_inner = false; if (get_debug_level() > 100) { std::cout << format("%s (%d): Iterating over %d input value pairs.", __FILE__, __LINE__, IO.size()) << std::endl; } // Iterate over the state variable inputs for (std::size_t i = 0; i < IO.size(); ++i) { // Reset the success indicator for the current state point success_inner = false; try { if (input_pair != INPUT_PAIR_INVALID && !all_trivial_outputs && !all_outputs_in_inputs) { // Update the state since it is a valid set of inputs if (!use_guesses || i == 0) { State->update(input_pair, in1[i], in2[i]); } else { State->update_with_guesses(input_pair, in1[i], in2[i], guesses); guesses.clear(); } } } catch (...) { if (one_input_one_output) { IO.clear(); throw; } // Re-raise the exception since we want to bubble the error // All the outputs are filled with _HUGE; go to next input for (std::size_t j = 0; j < IO[i].size(); ++j) { IO[i][j] = _HUGE; } continue; } for (std::size_t j = 0; j < IO[i].size(); ++j) { // If all the outputs are inputs, there is no need for a state input if (all_outputs_in_inputs) { if (p1 == output_parameters[j].Of1) { IO[i][j] = in1[i]; success_inner = true; continue; } else if (p2 == output_parameters[j].Of1) { IO[i][j] = in2[i]; success_inner = true; continue; } else { throw ValueError(); } } try { const output_parameter& output = output_parameters[j]; switch (output.type) { case output_parameter::OUTPUT_TYPE_TRIVIAL: case output_parameter::OUTPUT_TYPE_NORMAL: IO[i][j] = State->keyed_output(output.Of1); if (use_guesses) { switch (output.Of1) { case iDmolar: guesses.rhomolar = IO[i][j]; break; case iT: guesses.T = IO[i][j]; break; case iP: guesses.p = IO[i][j]; break; case iHmolar: guesses.hmolar = IO[i][j]; break; case iSmolar: guesses.smolar = IO[i][j]; break; default: throw ValueError("Don't understand this parameter"); } } break; case output_parameter::OUTPUT_TYPE_FIRST_DERIVATIVE: IO[i][j] = State->first_partial_deriv(output.Of1, output.Wrt1, output.Constant1); break; case output_parameter::OUTPUT_TYPE_FIRST_SATURATION_DERIVATIVE: IO[i][j] = State->first_saturation_deriv(output.Of1, output.Wrt1); break; case output_parameter::OUTPUT_TYPE_SECOND_DERIVATIVE: IO[i][j] = State->second_partial_deriv(output.Of1, output.Wrt1, output.Constant1, output.Wrt2, output.Constant2); break; default: throw ValueError(format("")); break; } // At least one has succeeded success_inner = true; } catch (...) { if (one_input_one_output) { IO.clear(); throw; } // Re-raise the exception since we want to bubble the error IO[i][j] = _HUGE; } } // We want to have at least rhomolar and T, but we do not raise errors here if (use_guesses && success_inner) { if (!ValidNumber(guesses.rhomolar)) { try { guesses.rhomolar = State->rhomolar(); } catch (...) { guesses.rhomolar = _HUGE; } } if (!ValidNumber(guesses.T)) { try { guesses.T = State->T(); } catch (...) { guesses.T = _HUGE; } } } // Save the success indicator, just a single valid output is enough success |= success_inner; } if (success == false) { IO.clear(); throw ValueError(format("No outputs were able to be calculated")); } } bool StripPhase(std::string& Name, shared_ptr& State) // Parses an imposed phase out of the Input Name string using the "|" delimiter { std::vector strVec = strsplit(Name, '|'); // Split input key string in to vector containing input key [0] and phase string [1] if (strVec.size() > 1) { // If there is a phase string (contains "|" character) // Check for invalid backends for setting phase in PropsSI const std::string strBackend = State->backend_name(); if (strBackend == get_backend_string(INCOMP_BACKEND)) throw ValueError("Cannot set phase on Incompressible Fluid; always liquid phase"); // incompressible fluids are always "liquid". if (strBackend == get_backend_string(IF97_BACKEND)) throw ValueError("Can't set phase on IF97 Backend"); // IF97 has to calculate it's own phase region if (strBackend == get_backend_string(TTSE_BACKEND)) throw ValueError("Can't set phase on TTSE Backend in PropsSI"); // Shouldn't be calling from High-Level anyway if (strBackend == get_backend_string(BICUBIC_BACKEND)) throw ValueError("Can't set phase on BICUBIC Backend in PropsSI"); // Shouldn't be calling from High-Level anyway if (strBackend == get_backend_string(VTPR_BACKEND)) throw ValueError("Can't set phase on VTPR Backend in PropsSI"); // VTPR has no phase functions to call phases imposed = iphase_not_imposed; // Initialize imposed phase if (strVec.size() > 2) // If there's more than on phase separator, throw error { throw ValueError(format("Invalid phase format: \"%s\"", Name)); } // Handle prefixes of iphase_, phase_, or std::basic_string::iterator str_Iter; std::string strPhase = strVec[1]; // Create a temp string so we can modify the prefix if (strPhase.find("iphase_") != strPhase.npos) { str_Iter = strPhase.erase(strPhase.begin()); } // Change "iphase_" to "phase_" if (strPhase.find("phase_") == strPhase.npos) { strPhase.insert(0, "phase_"); } // Prefix with "phase_" if missing // See if phase is a valid phase string, updating imposed while we're at it... if (!is_valid_phase(strPhase, imposed)) { throw ValueError(format("Phase string \"%s\" is not a valid phase", strVec[1])); // throw error with original string if not valid } // Parsed phase string was valid Name = strVec[0]; // Update input name to just the key string part State->specify_phase(imposed); // Update the specified phase on the backend State return true; // Return true because a valid phase string was found } return false; // Return false if there was no phase string on this key. } void _PropsSImulti(const std::vector& Outputs, const std::string& Name1, const std::vector& Prop1, const std::string& Name2, const std::vector& Prop2, const std::string& backend, const std::vector& fluids, const std::vector& fractions, std::vector>& IO) { shared_ptr State; CoolProp::parameters key1 = INVALID_PARAMETER, key2 = INVALID_PARAMETER; // Initialize to invalid parameter values CoolProp::input_pairs input_pair = INPUT_PAIR_INVALID; // Initialize to invalid input pair std::vector output_parameters; std::vector v1, v2; try { // Initialize the State class _PropsSI_initialize(backend, fluids, fractions, State); } catch (std::exception& e) { // Initialization failed. Stop. throw ValueError(format("Initialize failed for backend: \"%s\", fluid: \"%s\" fractions \"%s\"; error: %s", backend.c_str(), strjoin(fluids, "&").c_str(), vec_to_string(fractions, "%0.10f").c_str(), e.what())); } //strip any imposed phase from input key strings here std::string N1 = Name1; // Make Non-constant copy of Name1 that we can modify std::string N2 = Name2; // Make Non-constant copy of Name2 that we can modify const bool HasPhase1 = StripPhase(N1, State); // strip phase string from first name if needed const bool HasPhase2 = StripPhase(N2, State); // strip phase string from second name if needed if (HasPhase1 && HasPhase2) // if both Names have a phase string, don't allow it. throw ValueError("Phase can only be specified on one of the input key strings"); try { // Get update pair if (is_valid_parameter(N1, key1) && is_valid_parameter(N2, key2)) input_pair = generate_update_pair(key1, Prop1, key2, Prop2, v1, v2); } catch (std::exception& e) { // Input parameter parsing failed. Stop throw ValueError(format("Input pair parsing failed for Name1: \"%s\", Name2: \"%s\"; err: %s", Name1.c_str(), Name2.c_str(), e.what())); } try { output_parameters = output_parameter::get_output_parameters(Outputs); } catch (std::exception& e) { // Output parameter parsing failed. Stop. throw ValueError(format("Output parameter parsing failed; error: %s", e.what())); } // Calculate the output(s). In the case of a failure, individual values will be filled with _HUGE // Exception: If there is only one input and one output (like from PropsSI), the output will be cleared and IO.empty() will be true _PropsSI_outputs(State, output_parameters, input_pair, v1, v2, IO); } std::vector> PropsSImulti(const std::vector& Outputs, const std::string& Name1, const std::vector& Prop1, const std::string& Name2, const std::vector& Prop2, const std::string& backend, const std::vector& fluids, const std::vector& fractions) { std::vector> IO; #if !defined(NO_ERROR_CATCHING) try { #endif // Call the subfunction that can bubble errors _PropsSImulti(Outputs, Name1, Prop1, Name2, Prop2, backend, fluids, fractions, IO); // Return the value(s) return IO; #if !defined(NO_ERROR_CATCHING) } catch (const std::exception& e) { set_error_string(e.what()); # if defined(PROPSSI_ERROR_STDOUT) std::cout << e.what() << std::endl; # endif if (get_debug_level() > 1) { std::cout << e.what() << std::endl; } } catch (...) { } #endif return std::vector>(); } double PropsSI(const std::string& Output, const std::string& Name1, double Prop1, const std::string& Name2, double Prop2, const std::string& FluidName) { #if !defined(NO_ERROR_CATCHING) try { #endif // BEGIN OF TRY // Here is the real code that is inside the try block std::string backend, fluid; extract_backend(FluidName, backend, fluid); std::vector fractions(1, 1.0); // extract_fractions checks for has_fractions_in_string / has_solution_concentration; no need to double check const std::string fluid_string = extract_fractions(fluid, fractions); std::vector> IO; _PropsSImulti(strsplit(Output, '&'), Name1, std::vector(1, Prop1), Name2, std::vector(1, Prop2), backend, strsplit(fluid_string, '&'), fractions, IO); if (IO.empty()) { throw ValueError(get_global_param_string("errstring").c_str()); } if (IO.size() != 1 || IO[0].size() != 1) { throw ValueError(format("output should be 1x1; error was %s", get_global_param_string("errstring").c_str())); } const double val = IO[0][0]; if (get_debug_level() > 1) { std::cout << format("_PropsSI will return %g", val) << std::endl; } return val; // END OF TRY #if !defined(NO_ERROR_CATCHING) } catch (const std::exception& e) { set_error_string(e.what() + format(" : PropsSI(\"%s\",\"%s\",%0.10g,\"%s\",%0.10g,\"%s\")", Output.c_str(), Name1.c_str(), Prop1, Name2.c_str(), Prop2, FluidName.c_str())); # if defined(PROPSSI_ERROR_STDOUT) std::cout << e.what() << std::endl; # endif if (get_debug_level() > 1) { std::cout << e.what() << std::endl; } return _HUGE; } catch (...) { return _HUGE; } #endif } bool add_fluids_as_JSON(const std::string& backend, const std::string& fluidstring) { if (backend == "SRK" || backend == "PR") { CubicLibrary::add_fluids_as_JSON(fluidstring); return true; } else if (backend == "HEOS") { JSONFluidLibrary::add_many(fluidstring); return true; } else if (backend == "PCSAFT") { PCSAFTLibrary::add_fluids_as_JSON(fluidstring); return true; } else { throw ValueError(format("You have provided an invalid backend [%s] to add_fluids_as_JSON; valid options are SRK, PR, HEOS", backend.c_str())); } } // Simple function to check if REFPROPMixtureBackend is supported in this environment, so that we can skip // tests that require it in environments where it is not supported (e.g., CI builds on GitHub) void Skip_if_No_REFPROP() { #if defined(ENABLE_CATCH) if (!CoolProp::REFPROPMixtureBackend::REFPROP_supported()) SKIP("Skipping: REFPROP not supported in this environment."); #endif // Otherwise, in non-testing environments, this is a no-op. } #if defined(ENABLE_CATCH) TEST_CASE("Check inputs to PropsSI", "[PropsSI]") { SECTION("Single state, single output") { CHECK(ValidNumber(CoolProp::PropsSI("T", "P", 101325, "Q", 0, "Water"))); }; SECTION("Single state, single output, saturation derivative") { CHECK(ValidNumber(CoolProp::PropsSI("d(P)/d(T)|sigma", "P", 101325, "Q", 0, "Water"))); }; SECTION("Single state, single output, pure incompressible") { CHECK(ValidNumber(CoolProp::PropsSI("D", "P", 101325, "T", 300, "INCOMP::DowQ"))); }; SECTION("Single state, trivial output, pure incompressible") { CHECK(ValidNumber(CoolProp::PropsSI("Tmin", "P", 0, "T", 0, "INCOMP::DowQ"))); }; SECTION("Bad input pair") { CHECK(!ValidNumber(CoolProp::PropsSI("D", "Q", 0, "Q", 0, "Water"))); }; SECTION("Single state, single output, 40% incompressible") { CHECK(ValidNumber(CoolProp::PropsSI("D", "P", 101325, "T", 300, "INCOMP::MEG[0.40]"))); }; SECTION("Single state, single output, predefined CoolProp mixture") { CHECK(ValidNumber(CoolProp::PropsSI("T", "Q", 1, "P", 3e6, "HEOS::R125[0.7]&R32[0.3]"))); }; SECTION("Single state, single output") { CHECK(ValidNumber(CoolProp::PropsSI("T", "P", 101325, "Q", 0, "HEOS::Water"))); }; SECTION("Single state, single output, predefined mixture") { CHECK(ValidNumber(CoolProp::PropsSI("T", "P", 101325, "Q", 0, "R410A.mix"))); }; SECTION("Single state, single output, predefined mixture from REFPROP") { Skip_if_No_REFPROP(); CHECK(ValidNumber(CoolProp::PropsSI("T", "P", 101325, "Q", 0, "REFPROP::R410A.MIX"))); }; SECTION("Single state, single output, bad predefined mixture from REFPROP") { Skip_if_No_REFPROP(); CHECK(!ValidNumber(CoolProp::PropsSI("T", "P", 101325, "Q", 0, "REFPROP::RRRRRR.mix"))); }; SECTION("Predefined mixture") { std::vector p(1, 101325), Q(1, 1.0), z; std::vector outputs(1, "T"); outputs.push_back("Dmolar"); std::vector> IO; std::vector fluids(1, "R410A.mix"); CHECK_NOTHROW(IO = CoolProp::PropsSImulti(outputs, "P", p, "Q", Q, "HEOS", fluids, z)); }; SECTION("Single state, two outputs") { std::vector p(1, 101325), Q(1, 1.0), z(1, 1.0); std::vector outputs(1, "T"); outputs.push_back("Dmolar"); std::vector fluids(1, "Water"); CHECK_NOTHROW(CoolProp::PropsSImulti(outputs, "P", p, "Q", Q, "HEOS", fluids, z)); }; SECTION("Single state, two bad outputs") { std::vector p(1, 101325), Q(1, 1.0), z(1, 1.0); std::vector> IO; std::vector outputs(1, "???????"); outputs.push_back("?????????"); std::vector fluids(1, "Water"); CHECK_NOTHROW(IO = CoolProp::PropsSImulti(outputs, "P", p, "Q", Q, "HEOS", fluids, z)); CHECK(IO.size() == 0); }; SECTION("Two states, one output") { std::vector p(2, 101325), Q(2, 1.0), z(1, 1.0); std::vector outputs(1, "T"); std::vector fluids(1, "Water"); CHECK_NOTHROW(CoolProp::PropsSImulti(outputs, "P", p, "Q", Q, "HEOS", fluids, z)); }; SECTION("Two states, two outputs") { std::vector p(2, 101325), Q(2, 1.0), z(1, 1.0); std::vector outputs(1, "T"); outputs.push_back("Dmolar"); std::vector fluids(1, "Water"); CHECK_NOTHROW(CoolProp::PropsSImulti(outputs, "P", p, "Q", Q, "HEOS", fluids, z)); }; SECTION("cp and its derivative representation") { std::vector p(1, 101325), Q(1, 1.0), z(1, 1.0); std::vector> IO; std::vector outputs(1, "Cpmolar"); outputs.push_back("d(Hmolar)/d(T)|P"); std::vector fluids(1, "Water"); CHECK_NOTHROW(IO = CoolProp::PropsSImulti(outputs, "P", p, "Q", Q, "HEOS", fluids, z)); std::string errstring = get_global_param_string("errstring"); CAPTURE(errstring); REQUIRE(!IO.empty()); CAPTURE(IO[0][0]); CAPTURE(IO[0][1]); CHECK(std::abs(IO[0][0] - IO[0][1]) < 1e-5); }; SECTION("bad fluid") { std::vector p(1, 101325), Q(1, 1.0), z(1, 1.0); std::vector> IO; std::vector outputs(1, "Cpmolar"); outputs.push_back("d(Hmolar)/d(T)|P"); std::vector fluids(1, "????????"); CHECK_NOTHROW(IO = CoolProp::PropsSImulti(outputs, "P", p, "Q", Q, "HEOS", fluids, z)); std::string errstring = get_global_param_string("errstring"); CAPTURE(errstring); REQUIRE(IO.empty()); }; SECTION("bad mole fraction length") { std::vector p(1, 101325), Q(1, 1.0), z(1, 1.0); std::vector> IO; std::vector outputs(1, "T"); std::vector fluids(1, "Water&Ethanol"); CHECK_NOTHROW(IO = CoolProp::PropsSImulti(outputs, "P", p, "Q", Q, "HEOS", fluids, z)); std::string errstring = get_global_param_string("errstring"); CAPTURE(errstring); REQUIRE(IO.empty()); }; SECTION("bad input lengths") { std::vector p(1, 101325), Q(2, 1.0), z(100, 1.0); std::vector> IO; std::vector outputs(1, "Cpmolar"); outputs.push_back("d(Hmolar)/d(T)|P"); std::vector fluids(1, "Water"); CHECK_NOTHROW(IO = CoolProp::PropsSImulti(outputs, "P", p, "Q", Q, "HEOS", fluids, z)); std::string errstring = get_global_param_string("errstring"); CAPTURE(errstring); REQUIRE(IO.empty()); }; SECTION("bad input pair") { std::vector Q(2, 1.0), z(1, 1.0); std::vector> IO; std::vector outputs(1, "Cpmolar"); outputs.push_back("d(Hmolar)/d(T)|P"); std::vector fluids(1, "Water"); CHECK_NOTHROW(IO = CoolProp::PropsSImulti(outputs, "Q", Q, "Q", Q, "HEOS", fluids, z)); std::string errstring = get_global_param_string("errstring"); CAPTURE(errstring); REQUIRE(IO.empty()); }; }; #endif /**************************************************** * Props1SI * ****************************************************/ double Props1SI(std::string FluidName, std::string Output) { const bool valid_fluid1 = is_valid_fluid_string(FluidName); const bool valid_fluid2 = is_valid_fluid_string(Output); if (valid_fluid1 && valid_fluid2) { set_error_string(format("Both inputs to Props1SI [%s,%s] are valid fluids", Output.c_str(), FluidName.c_str())); return _HUGE; } if (!valid_fluid1 && !valid_fluid2) { set_error_string(format("Neither input to Props1SI [%s,%s] is a valid fluid", Output.c_str(), FluidName.c_str())); return _HUGE; } if (!valid_fluid1 && valid_fluid2) { // They are backwards, swap std::swap(Output, FluidName); } // First input is the fluid, second input is the input parameter const double val1 = PropsSI(Output, "", 0, "", 0, FluidName); if (!ValidNumber(val1)) { set_error_string(format("Unable to use input parameter [%s] in Props1SI for fluid %s; error was %s", Output.c_str(), FluidName.c_str(), get_global_param_string("errstring").c_str())); return _HUGE; } else { return val1; } } std::vector> Props1SImulti(const std::vector& Outputs, const std::string& backend, const std::vector& fluids, const std::vector& fractions) { std::vector zero_vector(1, 0.); std::vector> val1 = PropsSImulti(Outputs, "", zero_vector, "", zero_vector, backend, fluids, fractions); // error handling is done in PropsSImulti, val1 will be an empty vector if an error occurred return val1; } #if defined(ENABLE_CATCH) TEST_CASE("Check inputs to Props1SI", "[Props1SI],[PropsSI]") { SECTION("Good fluid, good parameter") { CHECK(ValidNumber(CoolProp::Props1SI("Tcrit", "Water"))); }; SECTION("Good fluid, good parameter") { CHECK(ValidNumber(CoolProp::PropsSI("Tcrit", "", 0, "", 0, "Water"))); }; SECTION("Good fluid, good parameter, inverted") { CHECK(ValidNumber(CoolProp::Props1SI("Water", "Tcrit"))); }; SECTION("Good fluid, bad parameter") { CHECK(!ValidNumber(CoolProp::Props1SI("Water", "????????????"))); }; SECTION("Bad fluid, good parameter") { CHECK(!ValidNumber(CoolProp::Props1SI("?????", "Tcrit"))); }; }; #endif bool is_valid_fluid_string(const std::string& fluidstring) { try { std::string backend, fluid; std::vector fractions; // First try to extract backend and fractions extract_backend(fluidstring, backend, fluid); std::string fluid_string = extract_fractions(fluid, fractions); // We are going to let the factory function load the state shared_ptr State(AbstractState::factory(backend, fluid_string)); return true; } catch (...) { return false; } } double saturation_ancillary(const std::string& fluid_name, const std::string& output, int Q, const std::string& input, double value) { // Generate the state instance std::vector names(1, fluid_name); CoolProp::HelmholtzEOSMixtureBackend HEOS(names); parameters iInput = get_parameter_index(input); parameters iOutput = get_parameter_index(output); return HEOS.saturation_ancillary(iOutput, Q, iInput, value); } void set_reference_stateS(const std::string& FluidName, const std::string& reference_state) { std::string backend, fluid; extract_backend(FluidName, backend, fluid); if (backend == "REFPROP") { int ierr = 0, ixflag = 1; double h0 = 0, s0 = 0, t0 = 0, p0 = 0; char herr[255], hrf[4]; double x0[1] = {1}; const char* refstate = reference_state.c_str(); if (strlen(refstate) > 3) { if (reference_state == "ASHRAE") { strncpy(hrf, "ASH", sizeof(hrf) - 1); hrf[sizeof(hrf) - 1] = '\0'; } else { throw ValueError(format("Reference state string [%s] is more than 3 characters long", reference_state.c_str())); } } else { strncpy(hrf, refstate, sizeof(hrf) - 1); hrf[sizeof(hrf) - 1] = '\0'; } REFPROP_SETREF(hrf, ixflag, x0, h0, s0, t0, p0, ierr, herr, 3, 255); } else if (backend == "HEOS" || backend == "?") { CoolProp::HelmholtzEOSMixtureBackend HEOS(std::vector(1, fluid)); if (reference_state == "IIR") { if (HEOS.Ttriple() > 273.15) { throw ValueError(format("Cannot use IIR reference state; Ttriple [%Lg] is greater than 273.15 K", HEOS.Ttriple())); } HEOS.update(QT_INPUTS, 0, 273.15); // Get current values for the enthalpy and entropy double deltah = HEOS.hmass() - 200000; // offset from 200000 J/kg enthalpy double deltas = HEOS.smass() - 1000; // offset from 1000 J/kg/K entropy double delta_a1 = deltas / (HEOS.gas_constant() / HEOS.molar_mass()); double delta_a2 = -deltah / (HEOS.gas_constant() / HEOS.molar_mass() * HEOS.get_reducing_state().T); // Change the value in the library for the given fluid set_fluid_enthalpy_entropy_offset(fluid, delta_a1, delta_a2, "IIR"); if (get_debug_level() > 0) { std::cout << format("set offsets to %0.15g and %0.15g\n", delta_a1, delta_a2); } } else if (reference_state == "ASHRAE") { if (HEOS.Ttriple() > 233.15) { throw ValueError(format("Cannot use ASHRAE reference state; Ttriple [%Lg] is greater than than 233.15 K", HEOS.Ttriple())); } HEOS.update(QT_INPUTS, 0, 233.15); // Get current values for the enthalpy and entropy double deltah = HEOS.hmass() - 0; // offset from 0 J/kg enthalpy double deltas = HEOS.smass() - 0; // offset from 0 J/kg/K entropy double delta_a1 = deltas / (HEOS.gas_constant() / HEOS.molar_mass()); double delta_a2 = -deltah / (HEOS.gas_constant() / HEOS.molar_mass() * HEOS.get_reducing_state().T); // Change the value in the library for the given fluid set_fluid_enthalpy_entropy_offset(fluid, delta_a1, delta_a2, "ASHRAE"); if (get_debug_level() > 0) { std::cout << format("set offsets to %0.15g and %0.15g\n", delta_a1, delta_a2); } } else if (reference_state == "NBP") { if (HEOS.p_triple() > 101325) { throw ValueError(format("Cannot use NBP reference state; p_triple [%Lg Pa] is greater than than 101325 Pa", HEOS.p_triple())); } // Saturated liquid boiling point at 1 atmosphere HEOS.update(PQ_INPUTS, 101325, 0); double deltah = HEOS.hmass() - 0; // offset from 0 kJ/kg enthalpy double deltas = HEOS.smass() - 0; // offset from 0 kJ/kg/K entropy double delta_a1 = deltas / (HEOS.gas_constant() / HEOS.molar_mass()); double delta_a2 = -deltah / (HEOS.gas_constant() / HEOS.molar_mass() * HEOS.get_reducing_state().T); // Change the value in the library for the given fluid set_fluid_enthalpy_entropy_offset(fluid, delta_a1, delta_a2, "NBP"); if (get_debug_level() > 0) { std::cout << format("set offsets to %0.15g and %0.15g\n", delta_a1, delta_a2); } } else if (reference_state == "DEF") { set_fluid_enthalpy_entropy_offset(fluid, 0, 0, "DEF"); } else if (reference_state == "RESET") { set_fluid_enthalpy_entropy_offset(fluid, 0, 0, "RESET"); } else { throw ValueError(format("Reference state string is invalid: [%s]", reference_state.c_str())); } } } void set_reference_stateD(const std::string& FluidName, double T, double rhomolar, double hmolar0, double smolar0) { std::vector _comps(1, FluidName); CoolProp::HelmholtzEOSMixtureBackend HEOS(_comps); HEOS.update(DmolarT_INPUTS, rhomolar, T); // Get current values for the enthalpy and entropy double deltah = HEOS.hmolar() - hmolar0; // offset from specified enthalpy in J/mol double deltas = HEOS.smolar() - smolar0; // offset from specified entropy in J/mol/K double delta_a1 = deltas / (HEOS.gas_constant()); double delta_a2 = -deltah / (HEOS.gas_constant() * HEOS.get_reducing_state().T); set_fluid_enthalpy_entropy_offset(FluidName, delta_a1, delta_a2, "custom"); } std::string get_global_param_string(const std::string& ParamName) { if (ParamName == "version") { return version; } else if (ParamName == "gitrevision") { return gitrevision; } else if (ParamName == "errstring") { std::string temp = error_string; error_string = ""; return temp; } else if (ParamName == "warnstring") { std::string temp = warning_string; warning_string = ""; return temp; } else if (ParamName == "FluidsList" || ParamName == "fluids_list" || ParamName == "fluidslist") { return get_fluid_list(); } else if (ParamName == "incompressible_list_pure") { return get_incompressible_list_pure(); } else if (ParamName == "incompressible_list_solution") { return get_incompressible_list_solution(); } else if (ParamName == "mixture_binary_pairs_list") { return get_csv_mixture_binary_pairs(); } else if (ParamName == "parameter_list") { return get_csv_parameter_list(); } else if (ParamName == "predefined_mixtures") { return get_csv_predefined_mixtures(); } else if (ParamName == "HOME") { return get_home_dir(); } else if (ParamName == "REFPROP_version") { return REFPROPMixtureBackend::version(); } else if (ParamName == "cubic_fluids_schema") { return std::string{CoolProp::CubicLibrary::get_cubic_fluids_schema()}; } else if (ParamName == "cubic_fluids_list") { return CoolProp::CubicLibrary::get_cubic_fluids_list(); } else if (ParamName == "pcsaft_fluids_schema") { return std::string{CoolProp::PCSAFTLibrary::get_pcsaft_fluids_schema()}; } else { throw ValueError(format("Input parameter [%s] is invalid", ParamName.c_str())); } }; #if defined(ENABLE_CATCH) TEST_CASE("Check inputs to get_global_param_string", "[get_global_param_string]") { const int num_good_inputs = 8; std::string good_inputs[num_good_inputs] = { "version", "gitrevision", "fluids_list", "incompressible_list_pure", "incompressible_list_solution", "mixture_binary_pairs_list", "parameter_list", "predefined_mixtures"}; std::ostringstream ss3c; for (int i = 0; i < num_good_inputs; ++i) { ss3c << "Test for" << good_inputs[i]; SECTION(ss3c.str(), "") { CHECK_NOTHROW(CoolProp::get_global_param_string(good_inputs[i])); }; } CHECK_THROWS(CoolProp::get_global_param_string("")); }; #endif std::string get_fluid_param_string(const std::string& FluidName, const std::string& ParamName) { std::string backend, fluid; extract_backend(FluidName, backend, fluid); shared_ptr AS(CoolProp::AbstractState::factory(backend, fluid)); return AS->fluid_param_string(ParamName); } #if defined(ENABLE_CATCH) TEST_CASE("Check inputs to get_fluid_param_string", "[get_fluid_param_string]") { const int num_good_inputs = 10; std::string good_inputs[num_good_inputs] = {"aliases", "CAS", "ASHRAE34", "REFPROPName", "BibTeX-CONDUCTIVITY", "BibTeX-EOS", "BibTeX-CP0", "BibTeX-SURFACE_TENSION", "BibTeX-MELTING_LINE", "BibTeX-VISCOSITY"}; std::ostringstream ss3c; for (int i = 0; i < num_good_inputs; ++i) { ss3c << "Test for" << good_inputs[i]; SECTION(ss3c.str(), "") { CHECK_NOTHROW(CoolProp::get_fluid_param_string("Water", good_inputs[i])); }; } CHECK_THROWS(CoolProp::get_fluid_param_string("", "aliases")); CHECK_THROWS(CoolProp::get_fluid_param_string("Water", "")); CHECK_THROWS(CoolProp::get_fluid_param_string("Water", "BibTeX-")); CHECK(CoolProp::get_fluid_param_string("Water", "pure") == "true"); CHECK(CoolProp::get_fluid_param_string("R410A", "pure") == "false"); }; #endif std::string phase_lookup_string(phases Phase) { switch (Phase) { case iphase_liquid: ///< Liquid return "liquid"; case iphase_supercritical: ///< Supercritical (p > pc, T > Tc) return "supercritical"; case iphase_supercritical_gas: ///< Supercritical gas (p < pc, T > Tc) return "supercritical_gas"; case iphase_supercritical_liquid: ///< Supercritical liquid (p > pc, T < Tc) return "supercritical_liquid"; case iphase_critical_point: ///< At the critical point return "critical_point"; case iphase_gas: ///< Subcritical gas return "gas"; case iphase_twophase: ///< Twophase (between saturation curves - inclusive) return "twophase"; case iphase_unknown: ///< Unknown phase return "unknown"; case iphase_not_imposed: return "not_imposed"; } throw ValueError("I should never be thrown"); } std::string PhaseSI(const std::string& Name1, double Prop1, const std::string& Name2, double Prop2, const std::string& FluidName) { double Phase_double = PropsSI("Phase", Name1, Prop1, Name2, Prop2, FluidName); // Attempt to get "Phase" from PropsSI() if (!ValidNumber(Phase_double)) { // if the returned phase is invalid... std::string strPhase = phase_lookup_string(iphase_unknown); // phase is unknown. std::string strError = get_global_param_string("errstring").c_str(); // fetch waiting error string if (strError != "") { // if error string is not empty, strPhase.append(": " + strError); // append it to the phase string. } return strPhase; // return the "unknown" phase string } // else std::size_t Phase_int = static_cast(Phase_double); // convert returned phase to int return phase_lookup_string(static_cast(Phase_int)); // return phase as a string } /* std::string PhaseSI(const std::string &Name1, double Prop1, const std::string &Name2, double Prop2, const std::string &FluidName, const std::vector &z) { double Phase_double = PropsSI("Phase",Name1,Prop1,Name2,Prop2,FluidName,z); if (!ValidNumber(Phase_double)){ return "";} std::size_t Phase_int = static_cast(Phase_double); return phase_lookup_string(static_cast(Phase_int)); } */ } /* namespace CoolProp */