teqp 0.19.1
Loading...
Searching...
No Matches
multifluid.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "nlohmann/json.hpp"
4
5#include <Eigen/Dense>
6#include <string>
7#include <cmath>
8#include <optional>
9#include <variant>
10
11#include "teqp/types.hpp"
12#include "teqp/constants.hpp"
13#include "teqp/filesystem.hpp"
14#include "teqp/json_tools.hpp"
15#include "teqp/exceptions.hpp"
16
17#include "RPinterop/interop.hpp"
18
19#if defined(TEQP_MULTICOMPLEX_ENABLED)
20#include "MultiComplex/MultiComplex.hpp"
21#endif
22
26
27#include <boost/algorithm/string/join.hpp>
28
29#if defined(TEQP_MULTICOMPLEX_ENABLED)
30// See https://eigen.tuxfamily.org/dox/TopicCustomizing_CustomScalar.html
31namespace Eigen {
32 template<typename TN> struct NumTraits<mcx::MultiComplex<TN>> : NumTraits<double> // permits to get the epsilon, dummy_precision, lowest, highest functions
33 {
34 enum {
35 IsComplex = 1,
36 IsInteger = 0,
37 IsSigned = 1,
38 RequireInitialization = 1,
39 ReadCost = 1,
40 AddCost = 3,
41 MulCost = 3
42 };
43 };
44}
45#endif
46
47namespace teqp{
48
49template<typename EOSCollection>
51
52private:
53 const EOSCollection EOSs;
54public:
55 CorrespondingStatesContribution(EOSCollection&& EOSs) : EOSs(EOSs) {};
56
57 auto size() const { return EOSs.size(); }
58
59 template<typename TauType, typename DeltaType, typename MoleFractions>
60 auto alphar(const TauType& tau, const DeltaType& delta, const MoleFractions& molefracs) const {
61 using resulttype = std::common_type_t<decltype(tau), decltype(molefracs[0]), decltype(delta)>; // Type promotion, without the const-ness
62 resulttype alphar = 0.0;
63 auto N = molefracs.size();
64 for (auto i = 0U; i < N; ++i) {
65 alphar = alphar + molefracs[i] * EOSs[i].alphar(tau, delta);
66 }
67 return forceeval(alphar);
68 }
69
70 template<typename TauType, typename DeltaType>
71 auto alphari(const TauType& tau, const DeltaType& delta, std::size_t i) const {
72 return EOSs[i].alphar(tau, delta);
73 }
74
75 auto get_EOS(std::size_t i) const{
76 return EOSs[i];
77 }
78};
79
80template<typename FCollection, typename DepartureFunctionCollection>
82
83private:
84 const FCollection F;
85 const DepartureFunctionCollection funcs;
86public:
87 DepartureContribution(FCollection&& F, DepartureFunctionCollection&& funcs) : F(F), funcs(funcs) {};
88
89 const auto& get_F() const { return F; }
90
91 template<typename TauType, typename DeltaType, typename MoleFractions>
92 auto alphar(const TauType& tau, const DeltaType& delta, const MoleFractions& molefracs) const {
93 using resulttype = std::common_type_t<decltype(tau), decltype(molefracs[0]), decltype(delta)>; // Type promotion, without the const-ness
94 resulttype alphar = 0.0;
95 std::size_t N = molefracs.size();
96 for (auto i = 0U; i < N; ++i) {
97 for (auto j = i+1; j < N; ++j) {
98 alphar = alphar + molefracs[i] * molefracs[j] * F(i, j) * funcs[i][j].alphar(tau, delta);
99 }
100 }
101 return forceeval(alphar);
102 }
103
105 template<typename TauType, typename DeltaType>
106 auto get_alpharij(const std::size_t i, const std::size_t j, const TauType& tau, const DeltaType& delta) const {
107 std::size_t N = funcs.size();
108 if (i < 0 || j < 0){
109 throw teqp::InvalidArgument("i or j is negative");
110 }
111 if (i >= N || j >= N){
112 throw teqp::InvalidArgument("i or j is invalid; size is " + std::to_string(N));
113 }
114 return forceeval(funcs[i][j].alphar(tau, delta));
115 }
116};
117
118template<typename CorrespondingTerm, typename DepartureTerm>
120
121private:
122 std::string meta = "";
123public:
125 const CorrespondingTerm corr;
126 const DepartureTerm dep;
129
130 template<class VecType>
131 auto R(const VecType& molefracs) const {
132 return std::visit([&molefracs](const auto& el){ return el.get_R(molefracs); }, Rcalc);
133 }
134
136 void set_meta(const std::string& m) { meta = m; }
138 auto get_meta() const { return meta; }
140 const std::variant<double, std::string> get_BIP(const std::size_t &i, const std::size_t &j, const std::string& key) const{
141 if (key == "F" || key == "Fij"){
142 auto F = dep.get_F();
143 if (0 <= i && i < F.rows() && 0 <= j && j < F.cols()){
144 return F(i,j);
145 }
146 }
147 return redfunc.get_BIP(i, j, key);
148 }
149
151
152 template<typename TType, typename RhoType>
153 auto alphar(TType T,
154 const RhoType& rhovec,
155 const std::optional<typename RhoType::value_type> rhotot = std::nullopt) const
156 {
157 typename RhoType::value_type rhotot_ = (rhotot.has_value()) ? rhotot.value() : std::accumulate(std::begin(rhovec), std::end(rhovec), (decltype(rhovec[0]))0.0);
158 auto molefrac = rhovec / rhotot_;
159 return alphar(T, rhotot_, molefrac);
160 }
161
162 template<typename TType, typename RhoType, typename MoleFracType>
163 auto alphar(const TType &T,
164 const RhoType &rho,
165 const MoleFracType& molefrac) const
166 {
167 if (static_cast<std::size_t>(molefrac.size()) != corr.size()){
168 throw teqp::InvalidArgument("Wrong size of mole fractions; "+std::to_string(corr.size()) + " are loaded but "+std::to_string(molefrac.size()) + " were provided");
169 }
170 auto Tred = forceeval(redfunc.get_Tr(molefrac));
171 auto rhored = forceeval(redfunc.get_rhor(molefrac));
172 auto delta = forceeval(rho / rhored);
173 auto tau = forceeval(Tred / T);
174 auto val = corr.alphar(tau, delta, molefrac) + dep.alphar(tau, delta, molefrac);
175 return forceeval(val);
176 }
177
178 template<typename TType, typename RhoType, typename MoleFracType>
179 auto alphar_taudelta(const TType &tau,
180 const RhoType &delta,
181 const MoleFracType& molefrac) const
182 {
183 if (static_cast<std::size_t>(molefrac.size()) != corr.size()){
184 throw teqp::InvalidArgument("Wrong size of mole fractions; "+std::to_string(corr.size()) + " are loaded but "+std::to_string(molefrac.size()) + " were provided");
185 }
186 auto val = corr.alphar(tau, delta, molefrac) + dep.alphar(tau, delta, molefrac);
187 return forceeval(val);
188 }
189};
190
191
192/***
193* \brief Get the JSON data structure for a given departure function
194* \param name The name (or alias) of the departure function to be looked up
195* \parm path The root path to the fluid data, or alternatively, the path to the json file directly
196*/
197inline auto get_departure_json(const std::string& name, const std::string& path) {
198 std::string filepath = std::filesystem::is_regular_file(path) ? path : path + "/dev/mixtures/mixture_departure_functions.json";
199 nlohmann::json j = load_a_JSON_file(filepath);
200 std::string js = j.dump(2);
201 // First pass, direct name lookup
202 for (auto& el : j) {
203 if (el.at("Name") == name) {
204 return el;
205 }
206 }
207 // Second pass, iterate over aliases
208 for (auto& el : j) {
209 for (auto &alias : el.at("aliases")) {
210 if (alias == name) {
211 return el;
212 }
213 }
214 }
215 throw std::invalid_argument("Could not match the name: " + name + "when looking up departure function");
216}
217
218inline auto build_departure_function(const nlohmann::json& j) {
219 auto build_power = [&](auto term, auto& dep) {
220 std::size_t N = term["n"].size();
221
222 // Don't add a departure function if there are no coefficients provided
223 if (N == 0) {
224 return;
225 }
226
227 PowerEOSTerm eos;
228
229 auto eigorzero = [&term, &N](const std::string& name) -> Eigen::ArrayXd {
230 if (!term[name].empty()) {
231 return toeig(term[name]);
232 }
233 else {
234 return Eigen::ArrayXd::Zero(N);
235 }
236 };
237
238
239 eos.n = eigorzero("n");
240 eos.t = eigorzero("t");
241 eos.d = eigorzero("d");
242
243 Eigen::ArrayXd c(N), l(N); c.setZero();
244
245 int Nlzero = 0, Nlnonzero = 0;
246 bool contiguous_lzero = false;
247
248 if (term["l"].empty()) {
249 // exponential part not included
250 l.setZero();
251 if (!all_same_length(term, { "n","t","d" })) {
252 throw std::invalid_argument("Lengths are not all identical in polynomial-like term");
253 }
254 }
255 else {
256 if (!all_same_length(term, { "n","t","d","l"})) {
257 throw std::invalid_argument("Lengths are not all identical in exponential term");
258 }
259 l = toeig(term["l"]);
260 // l is included, use it to build c; c_i = 1 if l_i > 0, zero otherwise
261 for (auto i = 0; i < c.size(); ++i) {
262 if (l[i] > 0) {
263 c[i] = 1.0;
264 }
265 }
266
267 // See how many of the first entries have zero values for l_i
268 contiguous_lzero = (l[0] == 0);
269 for (auto i = 0; i < c.size(); ++i) {
270 if (l[i] == 0) {
271 Nlzero++;
272 }
273 }
274 }
275 Nlnonzero = static_cast<int>(l.size()) - Nlzero;
276
277 if (contiguous_lzero && (l.tail(Nlnonzero) == 0).any()) {
278 throw std::invalid_argument("If l_i has zero and non-zero values, the zero values need to come first");
279 }
280
281 eos.c = c;
282 eos.l = l;
283
284 eos.l_i = eos.l.cast<int>();
285
286 if (Nlzero + Nlnonzero != l.size()) {
287 throw std::invalid_argument("Somehow the l lengths don't add up");
288 }
289
290
291 if (((eos.l_i.cast<double>() - eos.l).cwiseAbs() > 0.0).any()) {
292 throw std::invalid_argument("Non-integer entry in l found");
293 }
294
295 // If a contiguous portion of the terms have values of l_i that are zero
296 // it is computationally advantageous to break up the evaluation into
297 // part that has just the n_i*tau^t_i*delta^d_i and the part with the
298 // exponential term exp(-delta^l_i)
299 if (l.sum() == 0) {
300 // No l term at all, just polynomial
301 JustPowerEOSTerm poly;
302 poly.n = eos.n;
303 poly.t = eos.t;
304 poly.d = eos.d;
305 dep.add_term(poly);
306 }
307 else if (l.sum() > 0 && contiguous_lzero){
308 JustPowerEOSTerm poly;
309 poly.n = eos.n.head(Nlzero);
310 poly.t = eos.t.head(Nlzero);
311 poly.d = eos.d.head(Nlzero);
312 dep.add_term(poly);
313
314 PowerEOSTerm e;
315 e.n = eos.n.tail(Nlnonzero);
316 e.t = eos.t.tail(Nlnonzero);
317 e.d = eos.d.tail(Nlnonzero);
318 e.c = eos.c.tail(Nlnonzero);
319 e.l = eos.l.tail(Nlnonzero);
320 e.l_i = eos.l_i.tail(Nlnonzero);
321 dep.add_term(e);
322 }
323 else {
324 // Don't try to get too clever, just add the departure term
325 dep.add_term(eos);
326 }
327 };
328
329 auto build_doubleexponential = [&](auto& term, auto& dep) {
330 if (!all_same_length(term, { "n","t","d","ld","gd","lt","gt" })) {
331 throw std::invalid_argument("Lengths are not all identical in double exponential term");
332 }
334 eos.n = toeig(term.at("n"));
335 eos.t = toeig(term.at("t"));
336 eos.d = toeig(term.at("d"));
337 eos.ld = toeig(term.at("ld"));
338 eos.gd = toeig(term.at("gd"));
339 eos.lt = toeig(term.at("lt"));
340 eos.gt = toeig(term.at("gt"));
341 eos.ld_i = eos.ld.cast<int>();
342 dep.add_term(eos);
343 };
344 auto build_Chebyshev2D = [&](auto& term, auto& dep) {
346 int Ntau = term.at("Ntau"); // Degree in tau (there will be Ntau+1 coefficients in the tau direction)
347 int Ndelta = term.at("Ndelta"); // Degree in delta (there will be Ndelta+1 coefficients in the delta direction)
348 Eigen::ArrayXd c = toeig(term.at("a"));
349 if ((Ntau + 1)*(Ndelta + 1) != c.size()){
350 throw std::invalid_argument("Provided length [" + std::to_string(c.size()) + "] is not equal to (Ntau+1)*(Ndelta+1)");
351 }
352 eos.a = c.reshaped(Ntau+1, Ndelta+1).eval(); // All in one long array, then reshaped
353 eos.taumin = term.at("taumin");
354 eos.taumax = term.at("taumax");
355 eos.deltamin = term.at("deltamin");
356 eos.deltamax = term.at("deltamax");
357 dep.add_term(eos);
358 };
359 //auto build_gaussian = [&](auto& term) {
360 // GaussianEOSTerm eos;
361 // eos.n = toeig(term["n"]);
362 // eos.t = toeig(term["t"]);
363 // eos.d = toeig(term["d"]);
364 // eos.eta = toeig(term["eta"]);
365 // eos.beta = toeig(term["beta"]);
366 // eos.gamma = toeig(term["gamma"]);
367 // eos.epsilon = toeig(term["epsilon"]);
368 // if (!all_same_length(term, { "n","t","d","eta","beta","gamma","epsilon" })) {
369 // throw std::invalid_argument("Lengths are not all identical in Gaussian term");
370 // }
371 // return eos;
372 //};
373 auto build_GERG2004 = [&](const auto& term, auto& dep) {
374 if (!all_same_length(term, { "n","t","d","eta","beta","gamma","epsilon" })) {
375 throw std::invalid_argument("Lengths are not all identical in GERG term");
376 }
377 int Npower = term["Npower"];
378 auto NGERG = static_cast<int>(term["n"].size()) - Npower;
379
380 PowerEOSTerm eos;
381 eos.n = toeig(term["n"]).head(Npower);
382 eos.t = toeig(term["t"]).head(Npower);
383 eos.d = toeig(term["d"]).head(Npower);
384 if (term.contains("l")) {
385 eos.l = toeig(term["l"]).head(Npower);
386 }
387 else {
388 eos.l = 0.0 * eos.n;
389 }
390 eos.c = (eos.l > 0).cast<int>().cast<double>();
391 eos.l_i = eos.l.cast<int>();
392 dep.add_term(eos);
393
395 e.n = toeig(term["n"]).tail(NGERG);
396 e.t = toeig(term["t"]).tail(NGERG);
397 e.d = toeig(term["d"]).tail(NGERG);
398 e.eta = toeig(term["eta"]).tail(NGERG);
399 e.beta = toeig(term["beta"]).tail(NGERG);
400 e.gamma = toeig(term["gamma"]).tail(NGERG);
401 e.epsilon = toeig(term["epsilon"]).tail(NGERG);
402 dep.add_term(e);
403 };
404 auto build_GaussianExponential = [&](const auto& term, auto& dep) {
405 if (!all_same_length(term, { "n","t","d","eta","beta","gamma","epsilon" })) {
406 throw std::invalid_argument("Lengths are not all identical in Gaussian+Exponential term");
407 }
408 int Npower = term["Npower"];
409 auto NGauss = static_cast<int>(term["n"].size()) - Npower;
410
411 PowerEOSTerm eos;
412 eos.n = toeig(term["n"]).head(Npower);
413 eos.t = toeig(term["t"]).head(Npower);
414 eos.d = toeig(term["d"]).head(Npower);
415 if (term.contains("l")) {
416 eos.l = toeig(term["l"]).head(Npower);
417 }
418 else {
419 eos.l = 0.0 * eos.n;
420 }
421 eos.c = (eos.l > 0).cast<int>().cast<double>();
422 eos.l_i = eos.l.cast<int>();
423 dep.add_term(eos);
424
426 e.n = toeig(term["n"]).tail(NGauss);
427 e.t = toeig(term["t"]).tail(NGauss);
428 e.d = toeig(term["d"]).tail(NGauss);
429 e.eta = toeig(term["eta"]).tail(NGauss);
430 e.beta = toeig(term["beta"]).tail(NGauss);
431 e.gamma = toeig(term["gamma"]).tail(NGauss);
432 e.epsilon = toeig(term["epsilon"]).tail(NGauss);
433 dep.add_term(e);
434 };
435
436 std::string type = j.at("type");
437 DepartureTerms dep;
438 if (type == "Exponential") {
439 build_power(j, dep);
440 }
441 else if (type == "DoubleExponential") {
442 build_doubleexponential(j, dep);
443 }
444 else if (type == "GERG-2004" || type == "GERG-2008") {
445 build_GERG2004(j, dep);
446 }
447 else if (type == "Gaussian+Exponential") {
448 build_GaussianExponential(j, dep);
449 }
450 else if (type == "Chebyshev2D") {
451 build_Chebyshev2D(j, dep);
452 }
453 else if (type == "none") {
454 dep.add_term(NullEOSTerm());
455 }
456 else {
457
458 std::vector<std::string> options = { "Exponential","GERG-2004","GERG-2008","Gaussian+Exponential", "none", "DoubleExponential","Chebyshev2D"};
459 throw std::invalid_argument("Bad departure term type: " + type + ". Options are {" + boost::algorithm::join(options, ",") + "}");
460 }
461 return dep;
462}
463
464inline auto get_departure_function_matrix(const nlohmann::json& depcollection, const nlohmann::json& BIPcollection, const std::vector<std::string>& components, const nlohmann::json& flags) {
465
466 // Allocate the matrix with default models
467 std::vector<std::vector<DepartureTerms>> funcs(components.size()); for (auto i = 0U; i < funcs.size(); ++i) { funcs[i].resize(funcs.size()); }
468
469 // Load the collection of data on departure functions
470
471 auto get_departure_json = [&depcollection](const std::string& Name) {
472 for (auto& el : depcollection) {
473 if (el["Name"] == Name) { return el; }
474 }
475 throw std::invalid_argument("Bad departure function name: "+Name);
476 };
477
478 auto funcsmeta = nlohmann::json::object();
479
480 for (auto i = 0U; i < funcs.size(); ++i) {
481 std::string istr = std::to_string(i);
482 if (funcsmeta.contains(istr)) { funcsmeta[istr] = {}; }
483 for (auto j = i + 1; j < funcs.size(); ++j) {
484 std::string jstr = std::to_string(j);
485 auto [BIP, swap_needed] = reducing::get_BIPdep(BIPcollection, { components[i], components[j] }, flags);
486 std::string funcname = BIP.contains("function") ? BIP["function"] : "";
487 nlohmann::json jj;
488 if (!funcname.empty()) {
489 jj = get_departure_json(funcname);
490 funcs[i][j] = build_departure_function(jj);
491 funcs[j][i] = build_departure_function(jj);
492 }
493 else {
494 funcs[i][j].add_term(NullEOSTerm());
495 funcs[j][i].add_term(NullEOSTerm());
496 }
497 funcsmeta[istr][jstr] = { {"departure", jj}, {"BIP", BIP} };
498 funcsmeta[istr][jstr]["BIP"]["swap_needed"] = swap_needed;
499 }
500 }
501 return std::make_tuple(funcs, funcsmeta);
502}
503
504inline auto get_EOS_terms(const nlohmann::json& j)
505{
506 auto alphar = j["EOS"][0]["alphar"];
507
508 // First check whether term type is allowed
509 const std::vector<std::string> allowed_types = { "ResidualHelmholtzPower", "ResidualHelmholtzGaussian", "ResidualHelmholtzNonAnalytic","ResidualHelmholtzGaoB", "ResidualHelmholtzLemmon2005", "ResidualHelmholtzExponential", "ResidualHelmholtzDoubleExponential" };
510
511 auto isallowed = [&](const auto& conventional_types, const std::string& name) {
512 for (auto& a : conventional_types) { if (name == a) { return true; }; } return false;
513 };
514
515 for (auto& term : alphar) {
516 std::string type = term["type"];
517 if (!isallowed(allowed_types, type)) {
518 std::string a = allowed_types[0]; for (auto i = 1U; i < allowed_types.size(); ++i) { a += "," + allowed_types[i]; }
519 throw std::invalid_argument("Bad type:" + type + "; allowed types are: {" + a + "}");
520 }
521 }
522
523 EOSTerms container;
524
525 auto build_power = [&](auto term, auto & container) {
526 std::size_t N = term["n"].size();
527
528 PowerEOSTerm eos;
529
530 auto eigorzero = [&term, &N](const std::string& name) -> Eigen::ArrayXd {
531 if (!term[name].empty()) {
532 return toeig(term[name]);
533 }
534 else {
535 return Eigen::ArrayXd::Zero(N);
536 }
537 };
538
539
540 eos.n = eigorzero("n");
541 eos.t = eigorzero("t");
542 eos.d = eigorzero("d");
543
544 Eigen::ArrayXd c(N), l(N); c.setZero();
545 int Nlzero = 0, Nlnonzero = 0;
546 bool contiguous_lzero;
547 if (term["l"].empty()) {
548 // exponential part not included
549 l.setZero();
550 }
551 else {
552 l = toeig(term["l"]);
553 // l is included, use it to build c; c_i = 1 if l_i > 0, zero otherwise
554 for (auto i = 0; i < c.size(); ++i) {
555 if (l[i] > 0) {
556 c[i] = 1.0;
557 }
558 }
559
560 // See how many of the first entries have zero values for l_i
561 contiguous_lzero = (l[0] == 0);
562 for (auto i = 0; i < c.size(); ++i) {
563 if (l[i] == 0) {
564 Nlzero++;
565 }
566 }
567 }
568 Nlnonzero = static_cast<int>(l.size()) - Nlzero;
569
570 eos.c = c;
571 eos.l = l;
572
573 eos.l_i = eos.l.cast<int>();
574
575 if (Nlzero + Nlnonzero != l.size()) {
576 throw std::invalid_argument("Somehow the l lengths don't add up");
577 }
578
579 if (((eos.l_i.cast<double>() - eos.l).cwiseAbs() > 0.0).any()) {
580 throw std::invalid_argument("Non-integer entry in l found");
581 }
582
583 // If a contiguous portion of the terms have values of l_i that are zero
584 // it is computationally advantageous to break up the evaluation into
585 // part that has just the n_i*tau^t_i*delta^d_i and the part with the
586 // exponential term exp(-delta^l_i)
587 if (l.sum() == 0) {
588 // No l term at all, just polynomial
589 JustPowerEOSTerm poly;
590 poly.n = eos.n;
591 poly.t = eos.t;
592 poly.d = eos.d;
593 container.add_term(poly);
594 }
595 else if (l.sum() > 0 && contiguous_lzero) {
596 JustPowerEOSTerm poly;
597 poly.n = eos.n.head(Nlzero);
598 poly.t = eos.t.head(Nlzero);
599 poly.d = eos.d.head(Nlzero);
600 container.add_term(poly);
601
602 PowerEOSTerm e;
603 e.n = eos.n.tail(Nlnonzero);
604 e.t = eos.t.tail(Nlnonzero);
605 e.d = eos.d.tail(Nlnonzero);
606 e.c = eos.c.tail(Nlnonzero);
607 e.l = eos.l.tail(Nlnonzero);
608 e.l_i = eos.l_i.tail(Nlnonzero);
609 container.add_term(e);
610 }
611 else {
612 // Don't try to get too clever, just add the term
613 container.add_term(eos);
614 }
615 };
616
617 auto build_Lemmon2005 = [&](auto term) {
619 eos.n = toeig(term["n"]);
620 eos.t = toeig(term["t"]);
621 eos.d = toeig(term["d"]);
622 eos.m = toeig(term["m"]);
623 eos.l = toeig(term["l"]);
624 eos.l_i = eos.l.cast<int>();
625 if (!all_same_length(term, { "n","t","d","m","l" })) {
626 throw std::invalid_argument("Lengths are not all identical in Lemmon2005 term");
627 }
628 if (((eos.l_i.cast<double>() - eos.l).cwiseAbs() > 0.0).any()) {
629 throw std::invalid_argument("Non-integer entry in l found");
630 }
631 return eos;
632 };
633
634 auto build_gaussian = [&](auto term) {
635 GaussianEOSTerm eos;
636 eos.n = toeig(term["n"]);
637 eos.t = toeig(term["t"]);
638 eos.d = toeig(term["d"]);
639 eos.eta = toeig(term["eta"]);
640 eos.beta = toeig(term["beta"]);
641 eos.gamma = toeig(term["gamma"]);
642 eos.epsilon = toeig(term["epsilon"]);
643 if (!all_same_length(term, { "n","t","d","eta","beta","gamma","epsilon" })) {
644 throw std::invalid_argument("Lengths are not all identical in Gaussian term");
645 }
646 return eos;
647 };
648
649 auto build_exponential = [&](auto term) {
651 eos.n = toeig(term["n"]);
652 eos.t = toeig(term["t"]);
653 eos.d = toeig(term["d"]);
654 eos.g = toeig(term["g"]);
655 eos.l = toeig(term["l"]);
656 eos.l_i = eos.l.cast<int>();
657 if (!all_same_length(term, { "n","t","d","g","l" })) {
658 throw std::invalid_argument("Lengths are not all identical in exponential term");
659 }
660 return eos;
661 };
662
663 auto build_doubleexponential = [&](auto& term) {
664 if (!all_same_length(term, { "n","t","d","ld","gd","lt","gt" })) {
665 throw std::invalid_argument("Lengths are not all identical in double exponential term");
666 }
668 eos.n = toeig(term.at("n"));
669 eos.t = toeig(term.at("t"));
670 eos.d = toeig(term.at("d"));
671 eos.ld = toeig(term.at("ld"));
672 eos.gd = toeig(term.at("gd"));
673 eos.lt = toeig(term.at("lt"));
674 eos.gt = toeig(term.at("gt"));
675 eos.ld_i = eos.ld.cast<int>();
676 return eos;
677 };
678
679 auto build_GaoB = [&](auto term) {
680 GaoBEOSTerm eos;
681 eos.n = toeig(term["n"]);
682 eos.t = toeig(term["t"]);
683 eos.d = toeig(term["d"]);
684 eos.eta = -toeig(term["eta"]); // Watch out for this sign flip!!
685 eos.beta = toeig(term["beta"]);
686 eos.gamma = toeig(term["gamma"]);
687 eos.epsilon = toeig(term["epsilon"]);
688 eos.b = toeig(term["b"]);
689 if (!all_same_length(term, { "n","t","d","eta","beta","gamma","epsilon","b" })) {
690 throw std::invalid_argument("Lengths are not all identical in GaoB term");
691 }
692 return eos;
693 };
694
696 auto build_na = [&](auto& term) {
698 eos.n = toeig(term["n"]);
699 eos.A = toeig(term["A"]);
700 eos.B = toeig(term["B"]);
701 eos.C = toeig(term["C"]);
702 eos.D = toeig(term["D"]);
703 eos.a = toeig(term["a"]);
704 eos.b = toeig(term["b"]);
705 eos.beta = toeig(term["beta"]);
706 if (!all_same_length(term, { "n","A","B","C","D","a","b","beta" })) {
707 throw std::invalid_argument("Lengths are not all identical in nonanalytic term");
708 }
709 return eos;
710 };
711
712 for (auto& term : alphar) {
713 std::string type = term["type"];
714 if (type == "ResidualHelmholtzPower") {
715 build_power(term, container);
716 }
717 else if (type == "ResidualHelmholtzGaussian") {
718 container.add_term(build_gaussian(term));
719 }
720 else if (type == "ResidualHelmholtzNonAnalytic") {
721 container.add_term(build_na(term));
722 }
723 else if (type == "ResidualHelmholtzLemmon2005") {
724 container.add_term(build_Lemmon2005(term));
725 }
726 else if (type == "ResidualHelmholtzGaoB") {
727 container.add_term(build_GaoB(term));
728 }
729 else if (type == "ResidualHelmholtzExponential") {
730 container.add_term(build_exponential(term));
731 }
732 else if (type == "ResidualHelmholtzDoubleExponential") {
733 container.add_term(build_doubleexponential(term));
734 }
735 else {
736 throw std::invalid_argument("Bad term type: "+type);
737 }
738 }
739 return container;
740}
741
742inline auto get_EOSs(const std::vector<nlohmann::json>& pureJSON) {
743 std::vector<EOSTerms> EOSs;
744 for (auto& j : pureJSON) {
745 auto term = get_EOS_terms(j);
746 EOSs.emplace_back(term);
747 }
748 return EOSs;
749}
750
751inline auto collect_component_json(const std::vector<std::string>& components, const std::string& root)
752{
753 std::vector<nlohmann::json> out;
754 for (auto c : components) {
755 // First we try to lookup the name as a path, which can be on the filesystem, or relative to the root for default name lookup
756 std::vector<std::filesystem::path> candidates = { c, root + "/dev/fluids/" + c + ".json" };
757 std::filesystem::path selected_path = "";
758 for (auto candidate : candidates) {
759 if (std::filesystem::is_regular_file(candidate)) {
760 selected_path = candidate;
761 break;
762 }
763 }
764 if (selected_path != "") {
765 out.push_back(load_a_JSON_file(selected_path.string()));
766 }
767 else {
768 throw std::invalid_argument("Could not load any of the candidates:" + c);
769 }
770 }
771 return out;
772}
773
774inline auto collect_identifiers(const std::vector<nlohmann::json>& pureJSON)
775{
776 std::vector<std::string> CAS, Name, REFPROP, hash;
777 for (auto j : pureJSON) {
778 auto INFO = j.at("INFO");
779 Name.push_back(INFO.at("NAME"));
780 CAS.push_back(INFO.at("CAS"));
781 REFPROP.push_back(INFO.at("REFPROP_NAME"));
782 if (INFO.contains("HASH")){
783 hash.push_back(INFO.at("HASH"));
784 }
785 }
786 std::map<std::string, std::vector<std::string>> result{
787 {"CAS", CAS},
788 {"Name", Name},
789 {"REFPROP", REFPROP}
790 };
791 if (hash.size() == result["CAS"].size()){
792 result["hash"] = hash;
793 }
794 return result;
795}
796
798template<typename mapvecstring>
799inline auto select_identifier(const nlohmann::json& BIPcollection, const mapvecstring& identifierset, const nlohmann::json& flags){
800 for (const auto &ident: identifierset){
801 std::string key; std::vector<std::string> identifiers;
802 std::tie(key, identifiers) = ident;
803 try{
804 for (auto i = 0U; i < identifiers.size(); ++i){
805 for (auto j = i+1; j < identifiers.size(); ++j){
806 const std::vector<std::string> pair = {identifiers[i], identifiers[j]};
807 reducing::get_BIPdep(BIPcollection, pair, flags);
808 }
809 }
810 return key;
811 }
812 catch(...){
813
814 }
815 }
816 std::string errmsg;
817 for (const auto& [k,v] : identifierset){
818 if (errmsg.empty()){
819 errmsg += k;
820 }else {
821 errmsg += "," + k;
822 }
823 }
824 throw std::invalid_argument("Unable to match any of the identifier options: " + errmsg);
825}
826
828inline auto build_alias_map(const std::string& root) {
829 std::map<std::string, std::string> aliasmap;
830 for (auto path : get_files_in_folder(root + "/dev/fluids", ".json")) {
831 auto j = load_a_JSON_file(path.string());
832 std::string REFPROP_name = j.at("INFO").at("REFPROP_NAME");
833 std::string name = j.at("INFO").at("NAME");
834 for (std::string k : {"NAME", "CAS", "REFPROP_NAME"}) {
835 std::string val = j.at("INFO").at(k);
836 // Skip REFPROP names that match the fluid itself
837 if (k == "REFPROP_NAME" && val == name) {
838 continue;
839 }
840 // Skip invalid REFPROP names
841 if (k == "REFPROP_NAME" && val == "N/A") {
842 continue;
843 }
844 if (aliasmap.count(val) > 0) {
845 throw std::invalid_argument("Duplicated reverse lookup identifier ["+k+"] found in file:" + path.string());
846 }
847 else {
848 aliasmap[val] = std::filesystem::absolute(path).string();
849 }
850 }
851 std::vector<std::string> aliases = j.at("INFO").at("ALIASES");
852
853 for (std::string alias : aliases) {
854 if (alias != REFPROP_name && alias != name) { // Don't add REFPROP name or base name, were already above to list of aliases
855 if (aliasmap.count(alias) > 0) {
856 throw std::invalid_argument("Duplicated alias [" + alias + "] found in file:" + path.string());
857 }
858 else {
859 aliasmap[alias] = std::filesystem::absolute(path).string();
860 }
861 }
862 }
863 }
864 return aliasmap;
865}
866
867
869inline auto _build_multifluid_model(const std::vector<nlohmann::json> &pureJSON, const nlohmann::json& BIPcollection, const nlohmann::json& depcollection, const nlohmann::json& flags = {}) {
870
871 auto get_Rvals = [](const std::vector<nlohmann::json> &pureJSON) -> std::vector<double>{
872 std::vector<double> o;
873 for (auto pure : pureJSON){
874 o.push_back(pure.at("EOS")[0].at("gas_constant"));
875 }
876 return o;
877 };
878
879 auto [Tc, vc] = reducing::get_Tcvc(pureJSON);
880 auto EOSs = get_EOSs(pureJSON);
881 // Array of gas constants for each fluid
882 auto Rvals = get_Rvals(pureJSON);
883
884 // Extract the set of possible identifiers to be used to match parameters
885 auto identifierset = collect_identifiers(pureJSON);
886 // Decide which identifier is to be used (Name, CAS, REFPROP name)
887 auto identifiers = identifierset[select_identifier(BIPcollection, identifierset, flags)];
888
889 // Things related to the mixture
890 auto F = reducing::get_F_matrix(BIPcollection, identifiers, flags);
891 auto [funcs, funcsmeta] = get_departure_function_matrix(depcollection, BIPcollection, identifiers, flags);
892 auto [betaT, gammaT, betaV, gammaV] = reducing::get_BIP_matrices(BIPcollection, identifiers, flags, Tc, vc);
893
894 multifluid::gasconstant::GasConstantCalculator Rcalc = multifluid::gasconstant::MoleFractionWeighted(Rvals);
895
896 if (flags.contains("Rmodel") && flags.at("Rmodel") == "CODATA"){
897 Rcalc = multifluid::gasconstant::CODATA();
898 }
899
900
901 nlohmann::json meta = {
902 {"pures", pureJSON},
903 {"mix", funcsmeta},
904 };
905
906 auto redfunc = ReducingFunctions(std::move(MultiFluidReducingFunction(betaT, gammaT, betaV, gammaV, Tc, vc)));
907
908 auto model = MultiFluid(
909 std::move(redfunc),
910 CorrespondingStatesContribution(std::move(EOSs)),
911 DepartureContribution(std::move(F), std::move(funcs)),
912 std::move(Rcalc)
913 );
914 model.set_meta(meta.dump(1));
915 return model;
916}
917
919inline auto build_multifluid_JSONstr(const std::vector<std::string>& componentJSON, const std::string& BIPJSON, const std::string& departureJSON, const nlohmann::json& flags = {}) {
920
921 // Mixture things
922 const auto BIPcollection = nlohmann::json::parse(BIPJSON);
923 const auto depcollection = nlohmann::json::parse(departureJSON);
924
925 // Pure fluids
926 std::vector<nlohmann::json> pureJSON;
927 for (auto& c : componentJSON) {
928 pureJSON.emplace_back(nlohmann::json::parse(c));
929 }
930 return _build_multifluid_model(pureJSON, BIPcollection, depcollection, flags);
931}
932
941inline auto make_pure_components_JSON(const nlohmann::json& components, const std::string& root){
942
943 std::vector<nlohmann::json> pureJSON;
944 if (!components.is_array()){
945 throw std::invalid_argument("Must be an array");
946 }
947 std::optional<decltype(build_alias_map(""))> optaliasmap;
948 for (const nlohmann::json& comp : components){
949 auto get_or_aliasmap = [&](){
950 try{
951 return multilevel_JSON_load(comp, root);
952 }
953 catch(...){
954 // Build the alias map if not already constructed
955 if (!optaliasmap){
956 optaliasmap = build_alias_map(root);
957 }
958 return multilevel_JSON_load(optaliasmap.value().at(comp), root);
959 }
960 };
961 if (comp.is_string()){
962 std::string contents = comp;
963 // Note: first arg to substr is first index to *keep*, no second arg so keep to the end
964 if (contents.find("PATH::") == 0){
965 pureJSON.push_back(load_a_JSON_file(contents.substr(6)));
966 }
967 else if (contents.find("FLDPATH::") == 0){
968 pureJSON.push_back(RPinterop::FLDfile(contents.substr(9)).make_json(""));
969 }
970 else if (contents.find("FLD::") == 0){
971 pureJSON.push_back(RPinterop::FLDfile(contents.substr(5)).make_json(""));
972 }
973 else{
974 pureJSON.push_back(get_or_aliasmap());
975 }
976 }
977 else{
978 pureJSON.push_back(get_or_aliasmap());
979 }
980 }
981 return pureJSON;
982}
983
984inline auto build_multifluid_model(const std::vector<std::string>& components, const std::string& root, const std::string& BIPcollectionpath = {}, const nlohmann::json& flags = {}, const std::string& departurepath = {}) {
985
986 // Convert the string representations to JSON using the existing routines (a bit slower, but more convenient, more DRY)
987 nlohmann::json BIPcollection = nlohmann::json::array();
988 nlohmann::json depcollection = nlohmann::json::array();
989 if (components.size() > 1){
990 nlohmann::json B = BIPcollectionpath, D = departurepath;
991 BIPcollection = multilevel_JSON_load(B, root + "/dev/mixtures/mixture_binary_pairs.json");
992 depcollection = multilevel_JSON_load(D, root + "/dev/mixtures/mixture_departure_functions.json");
993 }
994
995 return _build_multifluid_model(make_pure_components_JSON(components, root), BIPcollection, depcollection, flags);
996}
997
1006inline auto multifluidfactory(const nlohmann::json& spec) {
1007
1008 nlohmann::json flags = (spec.contains("flags")) ? spec.at("flags") : nlohmann::json();
1009
1010 // We are in the interop logical branch in which we will be invoking the REFPROP-interop code
1011 if (spec.contains("HMX.BNC")){
1012 std::vector<nlohmann::json> componentJSON;
1013 for (auto comp : spec.at("components")){
1014 componentJSON.push_back(RPinterop::FLDfile(comp).make_json(""));
1015 }
1016 auto [BIPcollection, depcollection] = RPinterop::HMXBNCfile(spec.at("HMX.BNC")).make_jsons();
1017 return _build_multifluid_model(componentJSON, BIPcollection, depcollection, flags);
1018 }
1019 else{
1020
1021 std::string root = (spec.contains("root")) ? spec.at("root") : "";
1022
1023 auto components = spec.at("components");
1024
1025 nlohmann::json BIPcollection = nlohmann::json::array();
1026 nlohmann::json depcollection = nlohmann::json::array();
1027 if (components.size() > 1){
1028 BIPcollection = multilevel_JSON_load(spec.at("BIP"), root + "/dev/mixtures/mixture_binary_pairs.json");
1029 depcollection = multilevel_JSON_load(spec.at("departure"), root + "/dev/mixtures/mixture_departure_functions.json");
1030 }
1031
1032 return _build_multifluid_model(make_pure_components_JSON(components, root), BIPcollection, depcollection, flags);
1033 }
1034}
1036inline auto multifluidfactory(const std::string& specstring) {
1037 return multifluidfactory(nlohmann::json::parse(specstring));
1038}
1039
1040
1041
1042//class DummyEOS {
1043//public:
1044// template<typename TType, typename RhoType> auto alphar(TType tau, const RhoType& delta) const { return tau * delta; }
1045//};
1046//class DummyReducingFunction {
1047//public:
1048// template<typename MoleFractions> auto get_Tr(const MoleFractions& molefracs) const { return molefracs[0]; }
1049// template<typename MoleFractions> auto get_rhor(const MoleFractions& molefracs) const { return molefracs[0]; }
1050//};
1051//inline auto build_dummy_multifluid_model(const std::vector<std::string>& components) {
1052// std::vector<DummyEOS> EOSs(2);
1053// std::vector<std::vector<DummyEOS>> funcs(2); for (auto i = 0; i < funcs.size(); ++i) { funcs[i].resize(funcs.size()); }
1054// std::vector<std::vector<double>> F(2); for (auto i = 0; i < F.size(); ++i) { F[i].resize(F.size()); }
1055//
1056// struct Fwrapper {
1057// private:
1058// const std::vector<std::vector<double>> F_;
1059// public:
1060// Fwrapper(const std::vector<std::vector<double>> &F) : F_(F){};
1061// auto operator ()(std::size_t i, std::size_t j) const{ return F_[i][j]; }
1062// };
1063// auto ff = Fwrapper(F);
1064// auto redfunc = DummyReducingFunction();
1065// return MultiFluid(std::move(redfunc), std::move(CorrespondingStatesContribution(std::move(EOSs))), std::move(DepartureContribution(std::move(ff), std::move(funcs))));
1066//}
1067//inline void test_dummy() {
1068// auto model = build_dummy_multifluid_model({ "A", "B" });
1069// std::valarray<double> rhovec = { 1.0, 2.0 };
1070// auto alphar = model.alphar(300.0, rhovec);
1071//}
1072
1073}; // namespace teqp
CorrespondingStatesContribution(EOSCollection &&EOSs)
auto alphari(const TauType &tau, const DeltaType &delta, std::size_t i) const
auto alphar(const TauType &tau, const DeltaType &delta, const MoleFractions &molefracs) const
auto get_EOS(std::size_t i) const
DepartureContribution(FCollection &&F, DepartureFunctionCollection &&funcs)
auto alphar(const TauType &tau, const DeltaType &delta, const MoleFractions &molefracs) const
auto get_alpharij(const std::size_t i, const std::size_t j, const TauType &tau, const DeltaType &delta) const
Call a single departure term at i,j.
const auto & get_F() const
auto add_term(Instance &&instance)
auto alphar(const TType &T, const RhoType &rho, const MoleFracType &molefrac) const
const std::variant< double, std::string > get_BIP(const std::size_t &i, const std::size_t &j, const std::string &key) const
Return a binary interaction parameter.
const CorrespondingTerm corr
auto get_meta() const
Get the metadata stored in string form.
const DepartureTerm dep
void set_meta(const std::string &m)
Store some sort of metadata in string form (perhaps a JSON representation of the model?...
MultiFluid(ReducingFunctions &&redfunc, CorrespondingTerm &&corr, DepartureTerm &&dep, GasConstantCalculator &&Rcalc)
multifluid::gasconstant::GasConstantCalculator GasConstantCalculator
const ReducingFunctions redfunc
const GasConstantCalculator Rcalc
auto alphar(TType T, const RhoType &rhovec, const std::optional< typename RhoType::value_type > rhotot=std::nullopt) const
auto alphar_taudelta(const TType &tau, const RhoType &delta, const MoleFracType &molefrac) const
auto R(const VecType &molefracs) const
auto get_rhor(const MoleFractions &molefracs) const
auto get_Tr(const MoleFractions &molefracs) const
auto get_BIP(const std::size_t &i, const std::size_t &j, const std::string &key) const
std::variant< MoleFractionWeighted, CODATA > GasConstantCalculator
auto get_Tcvc(const std::vector< nlohmann::json > &pureJSON)
auto get_F_matrix(const nlohmann::json &collection, const std::vector< std::string > &identifiers, const nlohmann::json &flags)
Get the matrix F of Fij factors multiplying the departure functions.
auto get_BIPdep(const nlohmann::json &collection, const std::vector< std::string > &identifiers, const nlohmann::json &flags)
auto get_BIP_matrices(const nlohmann::json &collection, const std::vector< std::string > &components, const nlohmann::json &flags, const Tcvec &Tc, const vcvec &vc)
Build the matrices of betaT, gammaT, betaV, gammaV for the multi-fluid model.
auto _build_multifluid_model(const std::vector< nlohmann::json > &pureJSON, const nlohmann::json &BIPcollection, const nlohmann::json &depcollection, const nlohmann::json &flags={})
Internal method for actually constructing the model with the provided JSON data structures.
auto get_EOS_terms(const nlohmann::json &j)
auto get_EOSs(const std::vector< nlohmann::json > &pureJSON)
auto build_alias_map(const std::string &root)
Build a reverse-lookup map for finding a fluid JSON structure given a backup identifier.
auto toeig(const std::vector< double > &v) -> Eigen::ArrayXd
Definition types.hpp:10
nlohmann::json load_a_JSON_file(const std::string &path)
Load a JSON file from a specified file.
auto collect_identifiers(const std::vector< nlohmann::json > &pureJSON)
auto build_multifluid_JSONstr(const std::vector< std::string > &componentJSON, const std::string &BIPJSON, const std::string &departureJSON, const nlohmann::json &flags={})
A builder function where the JSON-formatted strings are provided explicitly rather than file paths.
auto get_departure_json(const std::string &name, const std::string &path)
auto get_departure_function_matrix(const nlohmann::json &depcollection, const nlohmann::json &BIPcollection, const std::vector< std::string > &components, const nlohmann::json &flags)
auto build_departure_function(const nlohmann::json &j)
auto all_same_length(const nlohmann::json &j, const std::vector< std::string > &ks)
auto select_identifier(const nlohmann::json &BIPcollection, const mapvecstring &identifierset, const nlohmann::json &flags)
Iterate over the possible options for identifiers to determine which one will satisfy all the binary ...
auto multifluidfactory(const nlohmann::json &spec)
Load a model from a JSON data structure.
auto build_multifluid_model(const std::vector< std::string > &components, const std::string &root, const std::string &BIPcollectionpath={}, const nlohmann::json &flags={}, const std::string &departurepath={})
auto make_pure_components_JSON(const nlohmann::json &components, const std::string &root)
ReducingTermContainer< MultiFluidReducingFunction, MultiFluidInvariantReducingFunction > ReducingFunctions
auto collect_component_json(const std::vector< std::string > &components, const std::string &root)
auto multilevel_JSON_load(const nlohmann::json &j, const std::string &default_path)
auto forceeval(T &&expr)
Definition types.hpp:46