CoolProp 8.0.0
An open-source fluid property and humid air property database
MixtureParameters.cpp
Go to the documentation of this file.
1#include "MixtureParameters.h"
4#include "mixture_departure_functions_JSON.h" // Creates the variable mixture_departure_functions_JSON
5#include "mixture_binary_pairs_JSON.h" // Creates the variable mixture_binary_pairs_JSON
6#include "predefined_mixtures_JSON.h" // Makes a std::string variable called predefined_mixtures_JSON
7
8#include <mutex>
9
10namespace CoolProp {
11
17{
18 public:
19 std::map<std::string, Dictionary> predefined_mixture_map;
20
22 load_from_string(predefined_mixtures_JSON);
23 }
24
25 void load_from_string(const std::string_view& str) {
26 nlohmann::json doc = cpjson::parse(str);
27 load_from_JSON(doc);
28 }
29
30 void load_from_JSON(const nlohmann::json& doc) {
31 if (!doc.is_array() || !doc[0].is_object()) {
32 throw ValueError("You must provide an array of objects");
33 }
34 // Iterate over the papers in the listing
35 for (const auto& el : doc) {
36 // Instantiate the empty dictionary to be filled
37 Dictionary dict;
38 // Get the name
39 std::string name = cpjson::get_string(el, "name") + ".mix";
40 // Get the fluid names
41 dict.add_string_vector("fluids", cpjson::get_string_array(el, "fluids"));
42 // Get the mole fractions
43 dict.add_double_vector("mole_fractions", cpjson::get_double_array(el, "mole_fractions"));
44 // Add to the map
45 predefined_mixture_map.emplace(name, dict);
46 // Also add the uppercase version to the map
47 predefined_mixture_map.emplace(upper(name), dict);
48 }
49 }
50};
51static PredefinedMixturesLibrary predefined_mixtures_library;
52
54 std::vector<std::string> out;
55 out.reserve(predefined_mixtures_library.predefined_mixture_map.size());
56 for (const auto& [key, val] : predefined_mixtures_library.predefined_mixture_map) {
57 out.push_back(key);
58 }
59 return strjoin(out, ",");
60}
61
62bool is_predefined_mixture(const std::string& name, Dictionary& dict) {
63 auto iter = predefined_mixtures_library.predefined_mixture_map.find(name);
64 if (iter != predefined_mixtures_library.predefined_mixture_map.end()) {
65 dict = iter->second;
66 return true;
67 } else {
68 return false;
69 }
70}
71
72void set_predefined_mixtures(const std::string& string_data) {
73 // JSON-encoded string for binary interaction parameters
74 predefined_mixtures_library.load_from_string(string_data);
75}
76
82{
83 private:
85 std::map<std::vector<std::string>, std::vector<Dictionary>> m_binary_pair_map;
89 std::once_flag m_load_flag;
90
91 public:
92 std::map<std::vector<std::string>, std::vector<Dictionary>>& binary_pair_map() {
94 return m_binary_pair_map;
95 };
96
97 void load_from_string(const std::string_view& str) {
98 nlohmann::json doc = cpjson::parse(str);
99 load_from_JSON(doc);
100 }
101
103 std::call_once(m_load_flag, [this] { load_defaults(); });
104 }
105
106 // Load the defaults that come from the JSON-encoded string compiled into library
107 // as the variable mixture_departure_functions_JSON
109 load_from_string(mixture_binary_pairs_JSON);
110 }
111
116 void load_from_JSON(const nlohmann::json& doc) {
117
118 // Iterate over the papers in the listing
119 for (const auto& el : doc) {
120 // Get the empty dictionary to be filled by the appropriate reducing parameter filling function
121 Dictionary dict;
122
123 // Get the vector of CAS numbers
124 std::vector<std::string> CAS;
125 CAS.push_back(cpjson::get_string(el, "CAS1"));
126 CAS.push_back(cpjson::get_string(el, "CAS2"));
127 std::string name1 = cpjson::get_string(el, "Name1");
128 std::string name2 = cpjson::get_string(el, "Name2");
129
130 // Sort the CAS number vector
131 std::sort(CAS.begin(), CAS.end());
132
133 // A sort was carried out, names/CAS were swapped
134 bool swapped = CAS[0].compare(cpjson::get_string(el, "CAS1")) != 0;
135
136 if (swapped) {
137 std::swap(name1, name2);
138 }
139
140 // Populate the dictionary with common terms
141 dict.add_string("name1", name1);
142 dict.add_string("name2", name2);
143 dict.add_string("BibTeX", cpjson::get_string(el, "BibTeX"));
144 dict.add_number("F", cpjson::get_double(el, "F"));
145 if (std::abs(dict.get_number("F")) > DBL_EPSILON) {
146 dict.add_string("function", cpjson::get_string(el, "function"));
147 }
148
149 if (el.contains("xi") && el.contains("zeta")) {
150 dict.add_string("type", "Lemmon-xi-zeta");
151 // Air and HFC mixtures from Lemmon - we could also directly do the conversion
152 dict.add_number("xi", cpjson::get_double(el, "xi"));
153 dict.add_number("zeta", cpjson::get_double(el, "zeta"));
154 } else if (el.contains("gammaT") && el.contains("gammaV") && el.contains("betaT") && el.contains("betaV")) {
155 dict.add_string("type", "GERG-2008");
156 dict.add_number("gammaV", cpjson::get_double(el, "gammaV"));
157 dict.add_number("gammaT", cpjson::get_double(el, "gammaT"));
158
159 double betaV = cpjson::get_double(el, "betaV");
160 double betaT = cpjson::get_double(el, "betaT");
161 if (swapped) {
162 dict.add_number("betaV", 1 / betaV);
163 dict.add_number("betaT", 1 / betaT);
164 } else {
165 dict.add_number("betaV", betaV);
166 dict.add_number("betaT", betaT);
167 }
168 } else {
169 std::cout << "Loading error: binary pair of " << name1 << " & " << name2
170 << "does not provide either a) xi and zeta b) gammaT, gammaV, betaT, and betaV" << '\n';
171 continue;
172 }
173
174 auto it = m_binary_pair_map.find(CAS);
175 if (it == m_binary_pair_map.end()) {
176 // Add to binary pair map by creating one-element vector
177 m_binary_pair_map.emplace(CAS, std::vector<Dictionary>(1, dict));
178 } else {
179 if (get_config_bool(OVERWRITE_BINARY_INTERACTION)) {
180 // Already there, see http://www.cplusplus.com/reference/map/map/insert/, so we are going to pop it and overwrite it
181 m_binary_pair_map.erase(it);
182 std::pair<std::map<std::vector<std::string>, std::vector<Dictionary>>::iterator, bool> ret;
183 ret = m_binary_pair_map.emplace(CAS, std::vector<Dictionary>(1, dict));
184 assert(ret.second == true);
185 } else {
186 // Error if already in map!
187 throw ValueError(
188 format("CAS pair(%s,%s) already in binary interaction map; considering enabling configuration key OVERWRITE_BINARY_INTERACTION",
189 CAS[0].c_str(), CAS[1].c_str()));
190 }
191 }
192 }
193 }
195 void add_simple_mixing_rule(const std::string& identifier1, const std::string& identifier2, const std::string& rule) {
196 // Get the empty dictionary to be filled by the appropriate reducing parameter filling function
197 Dictionary dict;
198
199 // Get the names/CAS of the compounds
200 std::string CAS1, CAS2, name1 = identifier1, name2 = identifier2;
201 shared_ptr<CoolProp::HelmholtzEOSMixtureBackend> HEOS1, HEOS2;
202
203 std::vector<std::string> id1split = strsplit(identifier1, '-');
204 if (id1split.size() == 3) { // Check if identifier is in CAS format
205 CAS1 = identifier1;
206 } else {
207 std::vector<std::string> names1(1, identifier1);
208 HEOS1 = std::make_shared<CoolProp::HelmholtzEOSMixtureBackend>(names1);
209 CAS1 = HEOS1->fluid_param_string("CAS");
210 }
211
212 std::vector<std::string> id2split = strsplit(identifier2, '-');
213 if (id2split.size() == 3) { // Check if identifier is in CAS format
214 CAS2 = identifier2;
215 } else {
216 std::vector<std::string> names2(1, identifier2);
217 HEOS2 = std::make_shared<CoolProp::HelmholtzEOSMixtureBackend>(names2);
218 CAS2 = HEOS2->fluid_param_string("CAS");
219 }
220
221 // Get the vector of CAS numbers
222 std::vector<std::string> CAS;
223 CAS.push_back(CAS1);
224 CAS.push_back(CAS2);
225
226 // Sort the CAS number vector
227 std::sort(CAS.begin(), CAS.end());
228
229 // Swap fluid names if the CAS numbers were swapped
230 if (CAS[0] != CAS1) {
231 std::swap(name1, name2);
232 }
233
234 // Populate the dictionary with common terms
235 dict.add_string("name1", name1);
236 dict.add_string("name2", name2);
237 dict.add_string("BibTeX", "N/A - " + rule);
238 dict.add_number("F", 0);
239 dict.add_string("type", "GERG-2008");
240
241 if (rule == "linear") {
242 // Terms for linear mixing
243 HEOS1 = std::make_shared<CoolProp::HelmholtzEOSMixtureBackend>(std::vector<std::string>(1, name1));
244 HEOS2 = std::make_shared<CoolProp::HelmholtzEOSMixtureBackend>(std::vector<std::string>(1, name2));
245
246 dict.add_number("gammaT", 0.5 * (HEOS1->T_critical() + HEOS2->T_critical()) / sqrt(HEOS1->T_critical() * HEOS2->T_critical()));
247 double rhoc1 = HEOS1->rhomolar_critical(), rhoc2 = HEOS2->rhomolar_critical();
248 dict.add_number("gammaV", 4 * (1 / rhoc1 + 1 / rhoc2) / pow(1 / pow(rhoc1, 1.0 / 3.0) + 1 / pow(rhoc2, 1.0 / 3.0), 3));
249 dict.add_number("betaV", 1.0);
250 dict.add_number("betaT", 1.0);
251 } else if (rule == "Lorentz-Berthelot") {
252 // Terms for Lorentz-Berthelot quadratic mixing
253
254 dict.add_number("gammaT", 1.0);
255 dict.add_number("gammaV", 1.0);
256 dict.add_number("betaV", 1.0);
257 dict.add_number("betaT", 1.0);
258 } else {
259 throw ValueError(format("Your simple mixing rule [%s] was not understood", rule.c_str()));
260 }
261
262 auto it = m_binary_pair_map.find(CAS);
263 if (it == m_binary_pair_map.end()) {
264 // Add to binary pair map by creating one-element vector
265 m_binary_pair_map.emplace(CAS, std::vector<Dictionary>(1, dict));
266 } else {
267 if (get_config_bool(OVERWRITE_BINARY_INTERACTION)) {
268 // Already there, see http://www.cplusplus.com/reference/map/map/insert/, so we are going to pop it and overwrite it
269 m_binary_pair_map.erase(it);
270 std::pair<std::map<std::vector<std::string>, std::vector<Dictionary>>::iterator, bool> ret;
271 ret = m_binary_pair_map.emplace(CAS, std::vector<Dictionary>(1, dict));
272 assert(ret.second == true);
273 } else {
274 // Error if already in map!
275 throw ValueError(
276 format("CAS pair(%s,%s) already in binary interaction map; considering enabling configuration key OVERWRITE_BINARY_INTERACTION",
277 CAS[0].c_str(), CAS[1].c_str()));
278 }
279 }
280 }
281};
282// The modifiable parameter library
283static MixtureBinaryPairLibrary mixturebinarypairlibrary;
284
286void apply_simple_mixing_rule(const std::string& identifier1, const std::string& identifier2, const std::string& rule) {
287 mixturebinarypairlibrary.load_defaults_if_needed();
288 mixturebinarypairlibrary.add_simple_mixing_rule(identifier1, identifier2, rule);
289}
290
292
293 std::vector<std::string> out;
294 for (const auto& [key, val] : mixturebinarypairlibrary.binary_pair_map()) {
295 out.push_back(strjoin(key, "&"));
296 }
297 return strjoin(out, ",");
298}
299
300std::string get_mixture_binary_pair_data(const std::string& CAS1, const std::string& CAS2, const std::string& key) {
301 // Find pair
302 std::vector<std::string> CAS;
303 CAS.push_back(CAS1);
304 CAS.push_back(CAS2);
305
306 if (mixturebinarypairlibrary.binary_pair_map().find(CAS) != mixturebinarypairlibrary.binary_pair_map().end()) {
307 std::vector<Dictionary>& v = mixturebinarypairlibrary.binary_pair_map()[CAS];
308 try {
309 if (key == "name1") {
310 return v[0].get_string("name1");
311 } else if (key == "name2") {
312 return v[0].get_string("name2");
313 } else if (key == "BibTeX") {
314 return v[0].get_string("BibTeX");
315 } else if (key == "function") {
316 return v[0].get_string("function");
317 } else if (key == "type") {
318 return v[0].get_string("type");
319 } else if (key == "F") {
320 return format("%0.16g", v[0].get_double("F"));
321 } else if (key == "xi") {
322 return format("%0.16g", v[0].get_double("xi"));
323 } else if (key == "zeta") {
324 return format("%0.16g", v[0].get_double("zeta"));
325 } else if (key == "gammaT") {
326 return format("%0.16g", v[0].get_double("gammaT"));
327 } else if (key == "gammaV") {
328 return format("%0.16g", v[0].get_double("gammaV"));
329 } else if (key == "betaT") {
330 return format("%0.16g", v[0].get_double("betaT"));
331 } else if (key == "betaV") {
332 return format("%0.16g", v[0].get_double("betaV"));
333 } else {
334 }
335 } catch (...) { // NOLINT(bugprone-empty-catch)
336 // Dictionary lookup threw (e.g. key present but wrong type);
337 // fall through to the ValueError below so the caller sees a
338 // uniform "could not match the parameter" message.
339 }
340 throw ValueError(format("Could not match the parameter [%s] for the binary pair [%s,%s] - for now this is an error.", key.c_str(),
341 CAS1.c_str(), CAS2.c_str()));
342 } else {
343 // Sort, see if other order works properly
344 std::sort(CAS.begin(), CAS.end());
345 if (mixturebinarypairlibrary.binary_pair_map().find(CAS) != mixturebinarypairlibrary.binary_pair_map().end()) {
346 throw ValueError(format("Could not match the binary pair [%s,%s] - order of CAS numbers is backwards; found the swapped CAS numbers.",
347 CAS1.c_str(), CAS2.c_str()));
348 } else {
349 throw ValueError(format("Could not match the binary pair [%s,%s] - for now this is an error.", CAS1.c_str(), CAS2.c_str()));
350 }
351 }
352}
353void set_mixture_binary_pair_data(const std::string& CAS1, const std::string& CAS2, const std::string& key, const double value) {
354
355 // Find pair
356 std::vector<std::string> CAS;
357 CAS.push_back(CAS1);
358 CAS.push_back(CAS2);
359
360 if (mixturebinarypairlibrary.binary_pair_map().find(CAS) != mixturebinarypairlibrary.binary_pair_map().end()) {
361 std::vector<Dictionary>& v = mixturebinarypairlibrary.binary_pair_map()[CAS];
362 if (v[0].has_number(key)) {
363 v[0].add_number(key, value);
364 } else {
365 throw ValueError(format("Could not set the parameter [%s] for the binary pair [%s,%s] - for now this is an error", key.c_str(),
366 CAS1.c_str(), CAS2.c_str()));
367 }
368 } else {
369 // Sort, see if other order works properly
370 std::sort(CAS.begin(), CAS.end());
371 if (mixturebinarypairlibrary.binary_pair_map().find(CAS) != mixturebinarypairlibrary.binary_pair_map().end()) {
372 throw ValueError(format("Could not match the binary pair [%s,%s] - order of CAS numbers is backwards; found the swapped CAS numbers.",
373 CAS1.c_str(), CAS2.c_str()));
374 } else {
375 throw ValueError(format("Could not match the binary pair [%s,%s] - for now this is an error.", CAS1.c_str(), CAS2.c_str()));
376 }
377 }
378}
379
380std::string get_reducing_function_name(const std::string& CAS1, const std::string& CAS2) {
381
382 std::vector<std::string> CAS;
383 CAS.push_back(CAS1);
384 CAS.push_back(CAS2);
385
386 // Sort the CAS number vector - map is based on sorted CAS codes
387 std::sort(CAS.begin(), CAS.end());
388
389 if (mixturebinarypairlibrary.binary_pair_map().find(CAS) != mixturebinarypairlibrary.binary_pair_map().end()) {
390 return mixturebinarypairlibrary.binary_pair_map()[CAS][0].get_string("function");
391 } else {
392 throw ValueError(format("Could not match the binary pair [%s,%s] - for now this is an error.", CAS1.c_str(), CAS2.c_str()));
393 }
394}
395
396void set_interaction_parameters(const std::string& string_data) {
397 // JSON-encoded string for binary interaction parameters
398 mixturebinarypairlibrary.load_from_string(string_data);
399}
400
404{
405 private:
407 std::map<std::string, Dictionary> m_departure_function_map;
409 std::once_flag m_load_flag;
410
411 public:
412 std::map<std::string, Dictionary>& departure_function_map() {
414 return m_departure_function_map;
415 };
416
417 void load_from_string(const std::string_view& str) {
418 nlohmann::json doc = cpjson::parse(str);
419 load_from_JSON(doc);
420 }
421 void load_from_JSON(const nlohmann::json& doc) {
422 // Iterate over the departure functions in the listing
423 for (const auto& el : doc) {
424 // Get the empty dictionary to be filled in
425 Dictionary dict;
426
427 // Populate the dictionary with common terms
428 std::string Name = cpjson::get_string(el, "Name");
429 std::string type = cpjson::get_string(el, "type");
430 dict.add_string("Name", Name);
431 dict.add_string("BibTeX", cpjson::get_string(el, "BibTeX"));
432 dict.add_string_vector("aliases", cpjson::get_string_array(el, "aliases"));
433 dict.add_string("type", type);
434
435 // Terms for the power (common to both types)
439
440 // Now we need to load additional terms
441 if (!type.compare("GERG-2008")) {
442 // Number of terms that are power terms
443 dict.add_number("Npower", cpjson::get_double(el, "Npower"));
444 // Terms for the gaussian
445 dict.add_double_vector("eta", cpjson::get_double_array(el, "eta"));
446 dict.add_double_vector("epsilon", cpjson::get_double_array(el, "epsilon"));
447 dict.add_double_vector("beta", cpjson::get_double_array(el, "beta"));
448 dict.add_double_vector("gamma", cpjson::get_double_array(el, "gamma"));
449 } else if (type == "Gaussian+Exponential") {
450 // Number of terms that are power terms
451 dict.add_number("Npower", cpjson::get_double(el, "Npower"));
452 // The decay strength parameters
454 // Terms for the gaussian part
455 dict.add_double_vector("eta", cpjson::get_double_array(el, "eta"));
456 dict.add_double_vector("epsilon", cpjson::get_double_array(el, "epsilon"));
457 dict.add_double_vector("beta", cpjson::get_double_array(el, "beta"));
458 dict.add_double_vector("gamma", cpjson::get_double_array(el, "gamma"));
459 } else if (!type.compare("Exponential")) {
461 } else {
462 throw ValueError(format("It was not possible to parse departure function with type [%s]", type.c_str()));
463 }
464 // Add the normal name;
465 add_one(Name, dict);
466 std::vector<std::string> aliases = dict.get_string_vector("aliases");
467 // Add the aliases too;
468 for (const auto& alias : aliases) {
469 // Add the alias;
470 add_one(alias, dict);
471 }
472 }
473 }
474 void add_one(const std::string& name, Dictionary& dict) {
475
476 // Check if this name is already in use
477 auto it = m_departure_function_map.find(name);
478 if (it == m_departure_function_map.end()) {
479 // Not in map, add new entry to map with dictionary as value and Name as key
480 m_departure_function_map.insert(std::pair<std::string, Dictionary>(name, dict));
481 } else {
482 if (get_config_bool(OVERWRITE_DEPARTURE_FUNCTION)) {
483 // Already there, see http://www.cplusplus.com/reference/map/map/insert/
484 m_departure_function_map.erase(it);
485 std::pair<std::map<std::string, Dictionary>::iterator, bool> ret;
486 ret = m_departure_function_map.emplace(name, dict);
487 assert(ret.second == true);
488 } else {
489 // Error if already in map!
490 //
491 // Collect all the current names for departure functions for a nicer error message
492 std::vector<std::string> names;
493 names.reserve(m_departure_function_map.size());
494 for (const auto& [name, dict] : m_departure_function_map) {
495 names.push_back(name);
496 }
497 throw ValueError(format("Name of departure function [%s] is already loaded. Current departure function names are: %s", name.c_str(),
498 strjoin(names, ",").c_str()));
499 }
500 }
501 }
503 std::call_once(m_load_flag, [this] { load_defaults(); });
504 }
505 // Load the defaults that come from the JSON-encoded string compiled into library
506 // as the variable mixture_departure_functions_JSON
508 load_from_string(mixture_departure_functions_JSON);
509 }
510};
511static MixtureDepartureFunctionsLibrary mixturedeparturefunctionslibrary;
512
513DepartureFunction* get_departure_function(const std::string& Name) {
514 // Get the dictionary itself
515 Dictionary& dict_dep = mixturedeparturefunctionslibrary.departure_function_map()[Name];
516
517 if (dict_dep.is_empty()) {
518 throw ValueError(format("Departure function name [%s] seems to be invalid", Name.c_str()));
519 }
520
521 // These terms are common
522 std::vector<double> n = dict_dep.get_double_vector("n");
523 std::vector<double> d = dict_dep.get_double_vector("d");
524 std::vector<double> t = dict_dep.get_double_vector("t");
525
526 std::string type_dep = dict_dep.get_string("type");
527
528 if (!type_dep.compare("GERG-2008")) {
529 // Number of power terms needed
530 int Npower = static_cast<int>(dict_dep.get_number("Npower"));
531 // Terms for the gaussian
532 std::vector<double> eta = dict_dep.get_double_vector("eta");
533 std::vector<double> epsilon = dict_dep.get_double_vector("epsilon");
534 std::vector<double> beta = dict_dep.get_double_vector("beta");
535 std::vector<double> gamma = dict_dep.get_double_vector("gamma");
536 return new GERG2008DepartureFunction(n, d, t, eta, epsilon, beta, gamma, Npower);
537 } else if (!type_dep.compare("Exponential")) {
538 // Powers of the exponents inside the exponential term
539 std::vector<double> l = dict_dep.get_double_vector("l");
540 return new ExponentialDepartureFunction(n, d, t, l);
541 } else if (!type_dep.compare("Gaussian+Exponential")) {
542 // Number of power terms needed
543 int Npower = static_cast<int>(dict_dep.get_number("Npower"));
544 // Powers of the exponents inside the exponential term
545 std::vector<double> l = dict_dep.get_double_vector("l");
546 // Terms for the gaussian
547 std::vector<double> eta = dict_dep.get_double_vector("eta");
548 std::vector<double> epsilon = dict_dep.get_double_vector("epsilon");
549 std::vector<double> beta = dict_dep.get_double_vector("beta");
550 std::vector<double> gamma = dict_dep.get_double_vector("gamma");
551 return new GaussianExponentialDepartureFunction(n, d, t, l, eta, epsilon, beta, gamma, Npower);
552 } else {
553 throw ValueError();
554 }
555}
557
558 std::vector<CoolPropFluid> components = HEOS.get_components();
559
560 std::size_t N = components.size();
561
562 STLMatrix beta_v, gamma_v, beta_T, gamma_T;
563 beta_v.resize(N, std::vector<CoolPropDbl>(N, 0));
564 gamma_v.resize(N, std::vector<CoolPropDbl>(N, 0));
565 beta_T.resize(N, std::vector<CoolPropDbl>(N, 0));
566 gamma_T.resize(N, std::vector<CoolPropDbl>(N, 0));
567
568 HEOS.residual_helmholtz->Excess.resize(N);
569
570 for (std::size_t i = 0; i < N; ++i) {
571 for (std::size_t j = 0; j < N; ++j) {
572 if (i == j) {
573 continue;
574 }
575
576 std::string CAS1 = components[i].CAS;
577 std::vector<std::string> CAS(2, "");
578 CAS[0] = components[i].CAS;
579 CAS[1] = components[j].CAS;
580 std::sort(CAS.begin(), CAS.end());
581
582 // The variable swapped is true if a swap occurred.
583 bool swapped = (CAS1.compare(CAS[0]) != 0);
584
585 // ***************************************************
586 // Reducing parameters for binary pair
587 // ***************************************************
588
589 if (mixturebinarypairlibrary.binary_pair_map().find(CAS) == mixturebinarypairlibrary.binary_pair_map().end()) {
590 throw ValueError(format("Could not match the binary pair [%s,%s] - for now this is an error.", CAS[0].c_str(), CAS[1].c_str()));
591 }
592
593 // Get a reference to the first matching binary pair in the dictionary
594 Dictionary& dict_red = mixturebinarypairlibrary.binary_pair_map()[CAS][0];
595
596 // Get the name of the type being used, one of GERG-2008, Lemmon-xi-zeta, etc.
597 std::string type_red = dict_red.get_string("type");
598
599 if (!type_red.compare("GERG-2008")) {
600 if (swapped) {
601 beta_v[i][j] = 1 / dict_red.get_number("betaV");
602 beta_T[i][j] = 1 / dict_red.get_number("betaT");
603 } else {
604 beta_v[i][j] = dict_red.get_number("betaV");
605 beta_T[i][j] = dict_red.get_number("betaT");
606 }
607 gamma_v[i][j] = dict_red.get_number("gammaV");
608 gamma_T[i][j] = dict_red.get_number("gammaT");
609 } else if (!type_red.compare("Lemmon-xi-zeta")) {
610 LemmonAirHFCReducingFunction::convert_to_GERG(components, i, j, dict_red, beta_T[i][j], beta_v[i][j], gamma_T[i][j], gamma_v[i][j]);
611 } else {
612 throw ValueError(format("type [%s] for reducing function for pair [%s, %s] is invalid", type_red.c_str(),
613 dict_red.get_string("Name1").c_str(), dict_red.get_string("Name2").c_str()));
614 }
615 /*
616 if (i == 0){
617 std::cout << format("betaT %10.9Lg gammaT %10.9Lg betaV %10.9Lg gammaV %10.9Lg %s",
618 beta_T[i][j], gamma_T[i][j], beta_v[i][j], gamma_v[i][j], get_mixture_binary_pair_data(CAS[0],CAS[1],"gammaT").c_str()) << std::endl;
619 }
620 */
621
622 // ***************************************************
623 // Departure functions used in excess term
624 // ***************************************************
625
626 // Set the scaling factor F for the excess term
627 HEOS.residual_helmholtz->Excess.F[i][j] = dict_red.get_number("F");
628
629 if (std::abs(HEOS.residual_helmholtz->Excess.F[i][j]) < DBL_EPSILON) {
630 // Empty departure function that will just return 0
631 std::vector<double> n(1, 0), d(1, 1), t(1, 1), l(1, 0);
632 HEOS.residual_helmholtz->Excess.DepartureFunctionMatrix[i][j] = std::make_shared<ExponentialDepartureFunction>(n, d, t, l);
633 continue;
634 }
635
636 // Get the name of the departure function to be used for this binary pair
637 std::string Name = CoolProp::get_reducing_function_name(components[i].CAS, components[j].CAS);
638
639 HEOS.residual_helmholtz->Excess.DepartureFunctionMatrix[i][j].reset(get_departure_function(Name));
640 }
641 }
642 // We have obtained all the parameters needed for the reducing function, now set the reducing function for the mixture
643 HEOS.Reducing = std::make_shared<GERG2008ReducingFunction>(components, beta_v, gamma_v, beta_T, gamma_T);
644}
645
646void parse_HMX_BNC(const std::string& s, std::vector<REFPROP_binary_element>& BIP, std::vector<REFPROP_departure_function>& functions) {
647 // Capture the betas, gammas, Fij, models
648 bool block_started = false;
649 std::size_t i_started = 0, i_ended = 0, i = 0;
650 std::vector<std::string> lines = strsplit(s, '\n');
651 for (auto& line : lines) {
652 if (strstartswith(strstrip(line), "#BNC")) {
653 block_started = true;
654 i_started = i + 1;
655 }
656 if (block_started && strstrip(line).empty()) {
657 i_ended = i - 1;
658 break;
659 }
660 i++;
661 }
662 // Find the first line with a !
663 for (i = i_started; i < i_ended; ++i) {
664 if (strstrip(lines[i]) == "!") {
665 i_started = i;
666 break;
667 }
668 }
669 // Find all the lines that '!' - these are delimiters
670 std::vector<std::pair<std::size_t, std::size_t>> bounds; // The left and right indices (inclusive) that form a binary pair
671 std::size_t last_excalamation = i_started;
672 for (i = i_started; i <= i_ended; ++i) {
673 if (strstrip(lines[i]) == "!") {
674 bounds.push_back(std::make_pair<std::size_t, std::size_t>(last_excalamation + 1, i - 1));
675 last_excalamation = i;
676 }
677 }
678 // Parse each chunk
679 std::vector<REFPROP_binary_element> chunks;
680 for (const auto& [bstart, bend] : bounds) {
682 for (std::size_t i = bstart; i <= bend; ++i) {
683 // Store comments
684 if (strstartswith(lines[i], "?")) {
685 bnc.comments.push_back(lines[i]);
686 continue;
687 }
688 // Parse the line with the thermo BIP
689 if (lines[i].find('/') > 0) {
690 // Split at ' '
691 std::vector<std::string> bits = strsplit(strstrip(lines[i]), ' ');
692 // Remove empty elements
693 for (std::size_t j = bits.size() - 1; j > 0; --j) {
694 if (bits[j].empty()) {
695 bits.erase(bits.begin() + j);
696 }
697 }
698 // Get the line that contains the thermo BIP
699 if (bits[0].find('/') > 0 && bits[1].size() == 3) {
700 std::vector<std::string> theCAS = strsplit(bits[0], '/');
701 bnc.CAS1 = theCAS[0];
702 bnc.CAS2 = theCAS[1];
703 bnc.model = bits[1];
704 bnc.betaT = string2double(bits[2]);
705 bnc.gammaT = string2double(bits[3]);
706 bnc.betaV = string2double(bits[4]);
707 bnc.gammaV = string2double(bits[5]);
708 bnc.Fij = string2double(bits[6]);
709 break;
710 } else if (strstrip(bits[0]) == "CAS#") {
711 break;
712 } else {
713 throw CoolProp::ValueError(format("Unable to parse binary interaction line: %s", lines[i]));
714 }
715 }
716 }
717 if (!bnc.CAS1.empty()) {
718 BIP.push_back(bnc);
719 }
720 }
721
722 // ****************************************
723 // Parse the departure functions
724 // ****************************************
725 for (std::size_t i = i_ended + 1; i < lines.size(); ++i) {
726 std::size_t j_end = 0;
727 // Find the end of this block
728 for (j_end = i + 1; j_end < lines.size(); ++j_end) {
729 if (strstrip(lines[j_end]).empty()) {
730 j_end -= 1;
731 break;
732 }
733 }
734
735 if (strstartswith(lines[i], "#MXM")) {
737 dep.Npower = -1;
738 dep.model = std::string(lines[i + 1].begin(), lines[i + 1].begin() + 3);
739 dep.comments.push_back(lines[i + 1]);
740 for (std::size_t j = i + 2; j <= j_end; ++j) {
741 if (strstartswith(strstrip(lines[j]), "?")) {
742 dep.comments.push_back(lines[j]);
743 continue;
744 }
745 if (strstartswith(strstrip(lines[j]), "!")) {
746 j += 2; // Skip the BIP here, not used
747 continue;
748 }
749 std::vector<std::string> bits = strsplit(lines[j], ' ');
750 // Remove empty elements
751 for (std::size_t k = bits.size() - 1; k > 0; --k) {
752 if (bits[k].empty()) {
753 bits.erase(bits.begin() + k);
754 }
755 }
756
757 if (dep.Npower < 0) { // Not extracted yet, let's do it now
758 // Extract the number of terms
759 dep.Npower = static_cast<short>(strtol(bits[0].c_str(), nullptr, 10));
760 dep.Nterms_power = static_cast<short>(strtol(bits[1].c_str(), nullptr, 10));
761 dep.Nspecial = static_cast<short>(strtol(bits[3].c_str(), nullptr, 10));
762 dep.Nterms_special = static_cast<short>(strtol(bits[4].c_str(), nullptr, 10));
763 } else {
764 dep.a.push_back(string2double(bits[0]));
765 dep.t.push_back(string2double(bits[1]));
766 dep.d.push_back(string2double(bits[2]));
767 // Extracting "polynomial" terms
768 if (dep.Nterms_power == 4) {
769 dep.e.push_back(string2double(bits[3]));
770 }
771 if (dep.Nspecial > 0) {
772 if (dep.a.size() - 1 < static_cast<size_t>(dep.Npower)) {
773 dep.eta.push_back(0);
774 dep.epsilon.push_back(0);
775 dep.beta.push_back(0);
776 dep.gamma.push_back(0);
777 } else {
778 // Extracting "special" terms
779 dep.eta.push_back(string2double(bits[3]));
780 dep.epsilon.push_back(string2double(bits[4]));
781 dep.beta.push_back(string2double(bits[5]));
782 dep.gamma.push_back(string2double(bits[6]));
783 }
784 }
785 }
786 }
787 functions.push_back(dep);
788 }
789 }
790}
791
792void set_departure_functions(const std::string& string_data) {
793 if (string_data.find("#MXM") != std::string::npos) {
794 // REFPROP HMX.BNC file was provided
795 std::vector<REFPROP_binary_element> BIP;
796 std::vector<REFPROP_departure_function> functions;
797 parse_HMX_BNC(string_data, BIP, functions);
798
799 {
800 nlohmann::json doc = nlohmann::json::array();
801 for (const auto& bip : BIP) {
802 nlohmann::json el;
803 el["CAS1"] = bip.CAS1;
804 el["CAS2"] = bip.CAS2;
805 el["Name1"] = "??";
806 el["Name2"] = "??";
807 el["betaT"] = bip.betaT;
808 el["gammaT"] = bip.gammaT;
809 el["betaV"] = bip.betaV;
810 el["gammaV"] = bip.gammaV;
811 el["F"] = bip.Fij;
812 el["function"] = bip.model;
813 el["BibTeX"] = "(from HMX.BNC format)::" + strjoin(bip.comments, "\n");
814 doc.push_back(std::move(el));
815 }
816 mixturebinarypairlibrary.load_from_JSON(doc);
817 }
818 {
819 nlohmann::json doc = nlohmann::json::array();
820 for (const auto& func : functions) {
821 nlohmann::json el;
822 el["Name"] = func.model;
823 el["aliases"] = nlohmann::json::array();
824 el["n"] = func.a;
825 el["d"] = func.d;
826 el["t"] = func.t;
827 if (func.Nterms_special > 0 || func.Nterms_power == 3) {
828 el["type"] = "GERG-2008";
829 el["Npower"] = func.Npower;
830 if (func.Nterms_power == 3 && func.Nspecial == 0) {
831 std::vector<double> zeros(func.a.size(), 0.0);
832 el["eta"] = zeros;
833 el["epsilon"] = zeros;
834 el["beta"] = zeros;
835 el["gamma"] = zeros;
836 } else {
837 el["eta"] = func.eta;
838 el["epsilon"] = func.epsilon;
839 el["beta"] = func.beta;
840 el["gamma"] = func.gamma;
841 }
842 } else {
843 el["type"] = "Exponential";
844 el["l"] = func.e;
845 }
846 el["BibTeX"] = "(from HMX.BNC format)::" + strjoin(func.comments, "\n");
847 doc.push_back(std::move(el));
848 }
849 mixturedeparturefunctionslibrary.load_from_JSON(doc);
850 }
851 } else {
852 // JSON-encoded string for departure functions
853 mixturedeparturefunctionslibrary.load_from_string(string_data);
854 }
855}
856
857} /* namespace CoolProp */