CoolProp 8.0.0
An open-source fluid property and humid air property database
FluidLibrary.cpp
Go to the documentation of this file.
1
2#include "FluidLibrary.h"
3
5#include <mutex>
6
8
9#if defined(COOLPROP_NO_INCBIN)
10# define INCBIN_CONST
11# define INCBIN_ALIGN
12# include "all_fluids_CBOR.h"
13# undef INCBIN_CONST
14# undef INCBIN_ALIGN
15#else
16# include "incbin.h"
17// Use the magic of the incbin library to include the binary CBOR data
18# if defined(_MSC_VER)
19# include "all_fluids_CBOR.h"
20# else
21
22INCBIN(all_fluids_CBOR, "all_fluids.cbor");
23# endif
24#endif
25
26namespace CoolProp {
27
28static JSONFluidLibrary library;
29
30void load();
31
32// Thread-safe lazy initialization of the global fluid library. The
33// previous `if (library.is_empty()) load();` pattern races when multiple
34// threads hit a cold cache simultaneously — multiple threads see the
35// library empty, all call load() concurrently, and the resulting
36// concurrent inserts into string_to_index_map / fluid_map leave the
37// library partially populated (gh-2787). std::call_once ensures load()
38// runs exactly once even under concurrent first calls; if load() throws,
39// the flag is left unset and the next call will retry.
40static std::once_flag library_load_flag;
41
42static void ensure_library_loaded() {
43 std::call_once(library_load_flag, &load);
44}
45
46void load() {
47 if (getenv("COOLPROP_DISABLE_SUPERANCILLARIES_ENTIRELY")) {
48 std::cout << "CoolProp: superancillaries have been disabled because the COOLPROP_DISABLE_SUPERANCILLARIES_ENTIRELY environment variable has "
49 "been defined"
50 << '\n';
51 }
52 // Let a CBOR-decode failure propagate (it did, as a parse error, before this
53 // migration): under std::call_once a thrown load() leaves the once-flag unset
54 // so the error surfaces and a later call can retry, rather than silently
55 // leaving the global library empty. add_many keeps its original catch.
57 try {
58 library.add_many(dd);
59 } catch (std::exception& e) {
60 std::cout << e.what() << '\n';
61 }
62}
63
64void JSONFluidLibrary::set_fluid_enthalpy_entropy_offset(const std::string& fluid, double delta_a1, double delta_a2, const std::string& ref) {
65 // Try to find it
66 auto it = string_to_index_map.find(fluid);
67 if (it != string_to_index_map.end()) {
68 auto it2 = fluid_map.find(it->second);
69 // If it is found
70 if (it2 != fluid_map.end()) {
71 if (!ValidNumber(delta_a1) || !ValidNumber(delta_a2)) {
72 throw ValueError(format("Not possible to set reference state for fluid %s because offset values are NAN", fluid.c_str()));
73 }
74 it2->second.EOS().alpha0.EnthalpyEntropyOffset.set(delta_a1, delta_a2, ref);
75
76 // Note: cached caloric superancillaries are not invalidated here.
77 // They are reference-state-agnostic: the offset's effect on h(T)
78 // and s(T) along the saturation curve is exactly a constant
79 // (Δh = R·T_red·Δa2, Δs = −R·Δa1), so shapes are invariant and
80 // c0 is shifted at query time. The (a1, a2) stamp recorded at
81 // first build lets resolve_T_via_superancillary translate the
82 // user's target into the cache's frame. See #2773.
83
84 shared_ptr<CoolProp::HelmholtzEOSBackend> HEOS = std::make_shared<CoolProp::HelmholtzEOSBackend>(it2->second);
85 HEOS->specify_phase(iphase_gas); // Something homogeneous;
86 // Calculate the new enthalpy and entropy values
87 HEOS->update(DmolarT_INPUTS, it2->second.EOS().hs_anchor.rhomolar, it2->second.EOS().hs_anchor.T);
88 it2->second.EOS().hs_anchor.hmolar = HEOS->hmolar();
89 it2->second.EOS().hs_anchor.smolar = HEOS->smolar();
90
91 double f = (HEOS->name() == "Water" || HEOS->name() == "CarbonDioxide") ? 1.00001 : 1.0;
92
93 // Calculate the new enthalpy and entropy values at the reducing state
94 HEOS->update(DmolarT_INPUTS, it2->second.EOS().reduce.rhomolar * f, it2->second.EOS().reduce.T * f);
95 it2->second.EOS().reduce.hmolar = HEOS->hmolar();
96 it2->second.EOS().reduce.smolar = HEOS->smolar();
97
98 // Calculate the new enthalpy and entropy values at the critical state
99 HEOS->update(DmolarT_INPUTS, it2->second.crit.rhomolar * f, it2->second.crit.T * f);
100 it2->second.crit.hmolar = HEOS->hmolar();
101 it2->second.crit.smolar = HEOS->smolar();
102
103 // Calculate the new enthalpy and entropy values
104 HEOS->update(DmolarT_INPUTS, it2->second.triple_liquid.rhomolar, it2->second.triple_liquid.T);
105 it2->second.triple_liquid.hmolar = HEOS->hmolar();
106 it2->second.triple_liquid.smolar = HEOS->smolar();
107
108 // Calculate the new enthalpy and entropy values
109 HEOS->update(DmolarT_INPUTS, it2->second.triple_vapor.rhomolar, it2->second.triple_vapor.T);
110 it2->second.triple_vapor.hmolar = HEOS->hmolar();
111 it2->second.triple_vapor.smolar = HEOS->smolar();
112
113 if (!HEOS->is_pure()) {
114 // Calculate the new enthalpy and entropy values
115 HEOS->update(DmolarT_INPUTS, it2->second.EOS().max_sat_T.rhomolar, it2->second.EOS().max_sat_T.T);
116 it2->second.EOS().max_sat_T.hmolar = HEOS->hmolar();
117 it2->second.EOS().max_sat_T.smolar = HEOS->smolar();
118 // Calculate the new enthalpy and entropy values
119 HEOS->update(DmolarT_INPUTS, it2->second.EOS().max_sat_p.rhomolar, it2->second.EOS().max_sat_p.T);
120 it2->second.EOS().max_sat_p.hmolar = HEOS->hmolar();
121 it2->second.EOS().max_sat_p.smolar = HEOS->smolar();
122 }
123 } else {
124 throw ValueError(format("fluid [%s] was not found in JSONFluidLibrary", fluid.c_str()));
125 }
126 }
127}
128
130void JSONFluidLibrary::add_many(const std::string& JSON_string) {
131
132 // First load all the baseline fluids
133 ensure_library_loaded();
134
135 // Then, load the fluids we would like to add
136 nlohmann::json doc = cpjson::parse(JSON_string);
137 library.add_many(doc);
138};
139
140void JSONFluidLibrary::add_many(const nlohmann::json& listing) {
141 if (!listing.is_array()) {
142 add_one(listing);
143 return;
144 }
145 for (const auto& fluid_json : listing) {
146 add_one(fluid_json);
147 }
148};
149
150void JSONFluidLibrary::add_one(const nlohmann::json& fluid_json) {
151 _is_empty = false;
152
153 // The variable index is initialized to the size of the fluid_map.
154 // Since the first fluid_map key equals zero (0), index is initialized to the key
155 // value for the next fluid to be added. (e.g. fluid_map[0..140]; index = 141 )
156 std::size_t index = fluid_map.size();
157
158 CoolPropFluid fluid; // create a new CoolPropFluid object
159
160 // Assign the fluid properties based on the passed in fluid_json
161 // =============================================================
162 // Parse out Fluid name
163 fluid.name = fluid_json.at("INFO").at("NAME").get<std::string>();
164
165 // Push the fluid name onto the name_vector used for returning the full list of library fluids
166 // If it is found that this fluid already exists in the library, it will be popped back off below.
167 name_vector.push_back(fluid.name);
168
169 try {
170 // CAS number
171 if (!fluid_json.at("INFO").contains("CAS")) {
172 throw ValueError(format("fluid [%s] does not have \"CAS\" member", fluid.name.c_str()));
173 }
174 fluid.CAS = fluid_json.at("INFO").at("CAS").get<std::string>();
175
176 // REFPROP alias
177 if (!fluid_json.at("INFO").contains("REFPROP_NAME")) {
178 throw ValueError(format("fluid [%s] does not have \"REFPROP_NAME\" member", fluid.name.c_str()));
179 }
180 fluid.REFPROPname = fluid_json.at("INFO").at("REFPROP_NAME").get<std::string>();
181
182 // FORMULA
183 if (fluid_json.at("INFO").contains("FORMULA")) {
184 fluid.formula = cpjson::get_string(fluid_json.at("INFO"), "FORMULA");
185 } else {
186 fluid.formula = "N/A";
187 }
188
189 // Abstract references
190 if (fluid_json.at("INFO").contains("INCHI_STRING")) {
191 fluid.InChI = cpjson::get_string(fluid_json.at("INFO"), "INCHI_STRING");
192 } else {
193 fluid.InChI = "N/A";
194 }
195
196 if (fluid_json.at("INFO").contains("INCHI_KEY")) {
197 fluid.InChIKey = cpjson::get_string(fluid_json.at("INFO"), "INCHI_KEY");
198 } else {
199 fluid.InChIKey = "N/A";
200 }
201
202 if (fluid_json.at("INFO").contains("SMILES")) {
203 fluid.smiles = cpjson::get_string(fluid_json.at("INFO"), "SMILES");
204 } else {
205 fluid.smiles = "N/A";
206 }
207
208 if (fluid_json.at("INFO").contains("CHEMSPIDER_ID")) {
209 fluid.ChemSpider_id = cpjson::get_integer(fluid_json.at("INFO"), "CHEMSPIDER_ID");
210 } else {
211 fluid.ChemSpider_id = -1;
212 }
213
214 if (fluid_json.at("INFO").contains("2DPNG_URL")) {
215 fluid.TwoDPNG_URL = cpjson::get_string(fluid_json.at("INFO"), "2DPNG_URL");
216 } else {
217 fluid.TwoDPNG_URL = "N/A";
218 }
219
220 // Parse the environmental parameters
221 if (!(fluid_json.at("INFO").contains("ENVIRONMENTAL"))) {
222 if (get_debug_level() > 0) {
223 std::cout << format("Environmental data are missing for fluid [%s]\n", fluid.name.c_str());
224 }
225 } else {
226 parse_environmental(fluid_json.at("INFO").at("ENVIRONMENTAL"), fluid);
227 }
228
229 // Aliases
230 fluid.aliases = cpjson::get_string_array(fluid_json.at("INFO").at("ALIASES"));
231
232 // Critical state
233 if (!fluid_json.contains("STATES")) {
234 throw ValueError(format("fluid [%s] does not have \"STATES\" member", fluid.name.c_str()));
235 }
236 parse_states(fluid_json.at("STATES"), fluid);
237
238 if (get_debug_level() > 5) {
239 std::cout << format("Loading fluid %s with CAS %s; %d fluids loaded\n", fluid.name.c_str(), fluid.CAS.c_str(), index);
240 }
241
242 // EOS
243 parse_EOS_listing(fluid_json.at("EOS"), fluid);
244
245 // Validate the fluid
246 validate(fluid);
247
248 // Ancillaries for saturation
249 if (!fluid_json.contains("ANCILLARIES")) {
250 throw ValueError(format("Ancillary curves are missing for fluid [%s]", fluid.name.c_str()));
251 };
252 parse_ancillaries(fluid_json.at("ANCILLARIES"), fluid);
253
254 // Surface tension
255 if (!(fluid_json.at("ANCILLARIES").contains("surface_tension"))) {
256 if (get_debug_level() > 0) {
257 std::cout << format("Surface tension curves are missing for fluid [%s]\n", fluid.name.c_str());
258 }
259 } else {
260 parse_surface_tension(fluid_json.at("ANCILLARIES").at("surface_tension"), fluid);
261 }
262
263 // Melting line
264 if (!(fluid_json.at("ANCILLARIES").contains("melting_line"))) {
265 if (get_debug_level() > 0) {
266 std::cout << format("Melting line curves are missing for fluid [%s]\n", fluid.name.c_str());
267 }
268 } else {
269 parse_melting_line(fluid_json.at("ANCILLARIES").at("melting_line"), fluid);
270 }
271
272 // Parse the transport property (viscosity and/or thermal conductivity) parameters
273 if (!(fluid_json.contains("TRANSPORT"))) {
274 default_transport(fluid);
275 } else {
276 parse_transport(fluid_json.at("TRANSPORT"), fluid);
277 }
278
279 // If the fluid is ok...
280
281 // First check that none of the identifiers are already present
282 // ===============================================================
283 // Remember that index is already initialized to fluid_map.size() = max index + 1.
284 // If the new fluid name, CAS, or aliases are found in the string_to_index_map, then
285 // the fluid is already in the fluid_map, so reset index to it's key.
286
287 if (string_to_index_map.find(fluid.CAS) != string_to_index_map.end()) {
288 index = string_to_index_map.find(fluid.CAS)->second; //if CAS found, grab index
289 } else if (string_to_index_map.find(fluid.name) != string_to_index_map.end()) {
290 index = string_to_index_map.find(fluid.name)->second; // if name found, grab index
291 } else if (string_to_index_map.find(upper(fluid.name)) != string_to_index_map.end()) {
292 index = string_to_index_map.find(upper(fluid.name))->second; // if uppercase name found, grab index
293 } else {
294 // Check the aliases
295 for (const auto& alias : fluid.aliases) {
296 if (string_to_index_map.find(alias) != string_to_index_map.end()) {
297 index = string_to_index_map.find(alias)->second; // if alias found, grab index
298 break;
299 }
300 if (string_to_index_map.find(upper(alias)) != string_to_index_map.end()) { // if ALIAS found, grab index
301 index = string_to_index_map.find(upper(alias))->second;
302 break;
303 }
304 }
305 }
306
307 bool fluid_exists = false; // Initialize flag for doing replace instead of add
308
309 if (index != fluid_map.size()) { // Fluid already in list if index was reset to something < fluid_map.size()
310 fluid_exists = true; // Set the flag for replace
311 name_vector.pop_back(); // Pop duplicate name off the back of the name vector; otherwise it keeps growing!
312 if (!get_config_bool(OVERWRITE_FLUIDS)) { // Throw exception if replacing fluids is not allowed
313 throw ValueError(format("Cannot load fluid [%s:%s] because it is already in library; index = [%i] of [%i]; Consider enabling the "
314 "config boolean variable OVERWRITE_FLUIDS",
315 fluid.name.c_str(), fluid.CAS.c_str(), index, fluid_map.size()));
316 }
317 }
318
319 // index now holds either
320 // 1. the index of a fluid that's already present, in which case it will be overwritten, or
321 // 2. the fluid_map.size(), in which case a new entry will be added to the list
322
323 // Add/Replace index->fluid mapping
324 // If the fluid index exists, the [] operator replaces the existing entry with the new fluid;
325 // However, since fluid is a custom type, the old entry must be erased first to properly
326 // release the memory before adding in the new fluid object at the same location (index)
327 if (fluid_exists) fluid_map.erase(fluid_map.find(index));
328 // if not, it will add the (index,fluid) pair to the map using the new index value (fluid_map.size())
329 fluid_map[index] = fluid;
330
331 // Add/Replace index->JSONstring mapping to easily pull out if the user wants it
332 // Convert fuid_json to a string and store it in the map at index.
333 // if the fluid index exists, the [] operator replaces the existing entry with the new JSONstring;
334 // However, since fluid_json is a custom type, the old entry must be erased first to properly
335 // release the memory before adding in the new fluid object at the same location (index)
336 if (fluid_exists) JSONstring_map.erase(JSONstring_map.find(index));
337 // if not, it will add the new (index,JSONstring) pair to the map.
338 JSONstring_map[index] = fluid_json.dump();
339
340 // Add/Replace CAS->index mapping
341 // This map helps find the index of a fluid in the fluid_map given a CAS string
342 // If the CAS string exists, the [] operator will replace index with an updated index number;
343 // if not, it will add a new (CAS,index) pair to the map.
344 string_to_index_map[fluid.CAS] = index;
345
346 // Add/Replace name->index mapping
347 // This map quickly finds the index of a fluid in the fluid_map given its name string
348 // Again, the map [] operator replaces if the alias is found, adds the new (name,index) pair if not
349 string_to_index_map[fluid.name] = index;
350
351 // Add/Replace the aliases->index mapping
352 // This map quickly finds the index of a fluid in the fluid_map given an alias string
353 // Again, the map [] operator replaces if the alias is found, adds the new (alias,index) pair if not
354 for (const auto& alias : fluid.aliases) {
355 string_to_index_map[alias] = index;
356
357 // Add uppercase alias for EES compatibility
358 string_to_index_map[upper(alias)] = index;
359 }
360
361 //If Debug level set >5 print fluid name and total size of fluid_map
362 if (get_debug_level() > 5) {
363 std::cout << format("Loaded fluid: %s - Number of fluids = %d\n", fluid.name, fluid_map.size());
364 }
365
366 } catch (const std::exception& e) {
367 throw ValueError(format("Unable to load fluid [%s] due to error: %s", fluid.name.c_str(), e.what()));
368 }
369};
370
372 ensure_library_loaded();
373 return library;
374}
375
376CoolPropFluid get_fluid(const std::string& fluid_string) {
377 ensure_library_loaded();
378 return library.get(fluid_string);
379}
380
381std::string get_fluid_as_JSONstring(const std::string& identifier) {
382 ensure_library_loaded();
383 return library.get_JSONstring(identifier);
384}
385
386std::string get_fluid_list() {
387 ensure_library_loaded();
388 return library.get_fluid_list();
389};
390
391void set_fluid_enthalpy_entropy_offset(const std::string& fluid, double delta_a1, double delta_a2, const std::string& ref) {
392 ensure_library_loaded();
393 library.set_fluid_enthalpy_entropy_offset(fluid, delta_a1, delta_a2, ref);
394}
395
396} /* namespace CoolProp */