CoolProp 8.0.0
An open-source fluid property and humid air property database
TabularBackends.h
Go to the documentation of this file.
1#ifndef TABULAR_BACKENDS_H
2#define TABULAR_BACKENDS_H
3
6#include <memory>
7using std::shared_ptr;
9#include "CoolProp/CoolProp.h"
10#include <optional>
11#include <utility>
14
19#define LIST_OF_MATRICES \
20 X(T) \
21 X(p) \
22 X(rhomolar) \
23 X(hmolar) \
24 X(smolar) \
25 X(umolar) \
26 X(dTdx) \
27 X(dTdy) \
28 X(dpdx) \
29 X(dpdy) \
30 X(drhomolardx) \
31 X(drhomolardy) \
32 X(dhmolardx) \
33 X(dhmolardy) \
34 X(dsmolardx) \
35 X(dsmolardy) \
36 X(dumolardx) \
37 X(dumolardy) \
38 X(d2Tdx2) \
39 X(d2Tdxdy) \
40 X(d2Tdy2) \
41 X(d2pdx2) \
42 X(d2pdxdy) \
43 X(d2pdy2) \
44 X(d2rhomolardx2) \
45 X(d2rhomolardxdy) \
46 X(d2rhomolardy2) \
47 X(d2hmolardx2) \
48 X(d2hmolardxdy) \
49 X(d2hmolardy2) \
50 X(d2smolardx2) \
51 X(d2smolardxdy) \
52 X(d2smolardy2) \
53 X(d2umolardx2) \
54 X(d2umolardxdy) \
55 X(d2umolardy2) \
56 X(visc) \
57 X(cond)
58
63#define LIST_OF_SATURATION_VECTORS \
64 X(TL) \
65 X(pL) \
66 X(logpL) \
67 X(hmolarL) \
68 X(smolarL) \
69 X(umolarL) \
70 X(rhomolarL) \
71 X(logrhomolarL) \
72 X(viscL) \
73 X(condL) \
74 X(logviscL) \
75 X(TV) \
76 X(pV) \
77 X(logpV) \
78 X(hmolarV) \
79 X(smolarV) \
80 X(umolarV) \
81 X(rhomolarV) \
82 X(logrhomolarV) \
83 X(viscV) \
84 X(condV) \
85 X(logviscV) \
86 X(cpmolarV) \
87 X(cpmolarL) \
88 X(cvmolarV) \
89 X(cvmolarL) \
90 X(speed_soundL) \
91 X(speed_soundV)
92
93namespace CoolProp {
94
96{
97
98 public:
100
102
104/* Use X macros to auto-generate the copying */
105#define X(name) name = PED.name;
107#undef X
108/* Use X macros to auto-generate the copying */
109#define X(name) name = PED.name;
111#undef X
112 };
113
114 std::map<std::string, std::vector<double>> vectors;
115 std::map<std::string, std::vector<std::vector<double>>> matrices;
116
117 MSGPACK_DEFINE(revision, vectors, matrices); // write the member variables that you want to pack using msgpack
118
120 void pack() {
121/* Use X macros to auto-generate the packing code; each will look something like: matrices.insert(std::pair<std::string, std::vector<double> >("T", T)); */
122#define X(name) vectors.insert(std::pair<std::string, std::vector<double>>(#name, name));
124#undef X
125/* Use X macros to auto-generate the packing code; each will look something like: matrices.insert(std::pair<std::string, std::vector<std::vector<CoolPropDbl> > >("T", T)); */
126#define X(name) matrices.insert(std::pair<std::string, std::vector<std::vector<double>>>(#name, name));
128#undef X
129 };
130 std::map<std::string, std::vector<double>>::iterator get_vector_iterator(const std::string& name) {
131 auto it = vectors.find(name);
132 if (it == vectors.end()) {
133 throw UnableToLoadError(format("could not find vector %s", name.c_str()));
134 }
135 return it;
136 }
137 std::map<std::string, std::vector<std::vector<double>>>::iterator get_matrix_iterator(const std::string& name) {
138 auto it = matrices.find(name);
139 if (it == matrices.end()) {
140 throw UnableToLoadError(format("could not find matrix %s", name.c_str()));
141 }
142 return it;
143 }
145 void unpack() {
146/* Use X macros to auto-generate the unpacking code;
147 * each will look something like: T = get_vector_iterator("T")->second
148 */
149#define X(name) name = get_vector_iterator(#name)->second;
151#undef X
152/* Use X macros to auto-generate the unpacking code;
153 * each will look something like: T = get_matrix_iterator("T")->second
154 **/
155#define X(name) name = get_matrix_iterator(#name)->second;
157#undef X
158 // Find the index of the point with the highest temperature
159 iTsat_max = std::distance(T.begin(), std::max_element(T.begin(), T.end()));
160 // Find the index of the point with the highest pressure
161 ipsat_max = std::distance(p.begin(), std::max_element(p.begin(), p.end()));
162 };
163 void deserialize(msgpack::object& deserialized) {
165 deserialized.convert(temp);
166 temp.unpack();
167 if (revision > temp.revision) {
168 throw ValueError(format("loaded revision [%d] is older than current revision [%d]", temp.revision, revision));
169 }
170 std::swap(*this, temp); // Swap if successful
171 };
172};
173
175inline void mass_to_molar(parameters& param, double& conversion_factor, double molar_mass) {
176 conversion_factor = 1.0;
177 switch (param) {
178 case iDmass:
179 conversion_factor = molar_mass;
180 param = iDmolar;
181 break;
182 case iHmass:
183 conversion_factor /= molar_mass;
184 param = iHmolar;
185 break;
186 case iSmass:
187 conversion_factor /= molar_mass;
188 param = iSmolar;
189 break;
190 case iUmass:
191 conversion_factor /= molar_mass;
192 param = iUmolar;
193 break;
194 case iCvmass:
195 conversion_factor /= molar_mass;
196 param = iCvmolar;
197 break;
198 case iCpmass:
199 conversion_factor /= molar_mass;
200 param = iCpmolar;
201 break;
202 case iDmolar:
203 case iHmolar:
204 case iSmolar:
205 case iUmolar:
206 case iCvmolar:
207 case iCpmolar:
208 case iT:
209 case iP:
210 case ispeed_sound:
213 case iviscosity:
214 case iconductivity:
215 return;
216 default:
217 throw ValueError("TabularBackends::mass_to_molar - I don't know how to convert this parameter");
218 }
219}
220
226{
227 public:
228 std::size_t N;
229 shared_ptr<CoolProp::AbstractState> AS;
230
232
234 void build(shared_ptr<CoolProp::AbstractState>& AS);
235
236/* Use X macros to auto-generate the variables; each will look something like: std::vector<double> T; */
237#define X(name) std::vector<double> name;
239#undef X
240
242 std::map<std::string, std::vector<double>> vectors;
243
244 MSGPACK_DEFINE(revision, vectors); // write the member variables that you want to pack
245
246 /***
247 * \brief Determine if a set of inputs are single-phase or inside the saturation table
248 * @param main The main variable that is being provided (currently T or P)
249 * @param mainval The value of the main variable that is being provided
250 * @param other The secondary variable
251 * @param val The value of the secondary variable
252 * @param iL The index associated with the nearest point for the liquid
253 * @param iV The index associated with the nearest point for the vapor
254 * @param yL The value associated with the nearest point for the liquid (based on interpolation)
255 * @param yV The value associated with the nearest point for the vapor (based on interpolation)
256
257 \note If PQ or QT are inputs, yL and yV will correspond to the other main variable: p->T or T->p
258 */
259 [[nodiscard]] bool is_inside(parameters main, double mainval, parameters other, double val, std::size_t& iL, std::size_t& iV, CoolPropDbl& yL,
260 CoolPropDbl& yV) {
261 auto [yvecL, yvecV] = [&]() -> std::pair<std::vector<double>*, std::vector<double>*> {
262 switch (other) {
263 case iT:
264 return {&TL, &TV};
265 case iHmolar:
266 return {&hmolarL, &hmolarV};
267 case iQ:
268 return {&TL, &TV};
269 case iSmolar:
270 return {&smolarL, &smolarV};
271 case iUmolar:
272 return {&umolarL, &umolarV};
273 case iDmolar:
274 return {&rhomolarL, &rhomolarV};
275 default:
276 throw ValueError("invalid input for other in is_inside");
277 }
278 }();
279
280 // Trivial checks
281 if (main == iP) {
282 // If p is outside the range (ptriple, pcrit), considered to not be inside
283 double pmax = this->pV[pV.size() - 1], pmin = this->pV[0];
284 if (mainval > pmax || mainval < pmin) {
285 return false;
286 }
287 } else if (main == iT) {
288 // If T is outside the range (Tmin, Tcrit), considered to not be inside
289 double Tmax = this->TV[TV.size() - 1], Tmin = this->TV[0];
290 if (mainval > Tmax || mainval < Tmin) {
291 return false;
292 }
293 } else {
294 throw ValueError("invalid input for other in is_inside");
295 }
296
297 // Now check based on a rough analysis using bounding pressure
298 std::size_t iLplus, iVplus;
299 // Find the indices (iL,iL+1) & (iV,iV+1) that bound the given pressure
300 // In general iV and iL will be the same, but if pseudo-pure, they might
301 // be different
302 if (main == iP) {
303 bisect_vector(pV, mainval, iV);
304 bisect_vector(pL, mainval, iL);
305 } else if (main == iT) {
306 bisect_vector(TV, mainval, iV);
307 bisect_vector(TL, mainval, iL);
308 } else {
309 throw ValueError(format("For now, main input in is_inside must be T or p"));
310 }
311
312 iVplus = std::min(iV + 1, N - 1);
313 iLplus = std::min(iL + 1, N - 1);
314 if (other == iQ) {
315 // Actually do "saturation" call using cubic interpolation
316 if (iVplus < 3) {
317 iVplus = 3;
318 }
319 if (iLplus < 3) {
320 iLplus = 3;
321 }
322 if (main == iP) {
323 double logp = log(mainval);
324 // Calculate temperature
325 yV = CubicInterp(logpV, TV, iVplus - 3, iVplus - 2, iVplus - 1, iVplus, logp);
326 yL = CubicInterp(logpL, TL, iLplus - 3, iLplus - 2, iLplus - 1, iLplus, logp);
327 } else if (main == iT) {
328 // Calculate pressure
329 yV = exp(CubicInterp(TV, logpV, iVplus - 3, iVplus - 2, iVplus - 1, iVplus, mainval));
330 yL = exp(CubicInterp(TL, logpL, iLplus - 3, iLplus - 2, iLplus - 1, iLplus, mainval));
331 }
332 return true;
333 }
334 // Find the bounding values for the other variable
335 double ymin = min4((*yvecL)[iL], (*yvecL)[iLplus], (*yvecV)[iV], (*yvecV)[iVplus]);
336 double ymax = max4((*yvecL)[iL], (*yvecL)[iLplus], (*yvecV)[iV], (*yvecV)[iVplus]);
337 if (val < ymin || val > ymax) {
338 return false;
339 }
340 // Actually do "saturation" call using cubic interpolation
341 if (iVplus < 3) {
342 iVplus = 3;
343 }
344 if (iLplus < 3) {
345 iLplus = 3;
346 }
347 if (main == iP) {
348 double logp = log(mainval);
349 yV = CubicInterp(logpV, *yvecV, iVplus - 3, iVplus - 2, iVplus - 1, iVplus, logp);
350 yL = CubicInterp(logpL, *yvecL, iLplus - 3, iLplus - 2, iLplus - 1, iLplus, logp);
351 } else if (main == iT) {
352 yV = CubicInterp(TV, *yvecV, iVplus - 3, iVplus - 2, iVplus - 1, iVplus, mainval);
353 yL = CubicInterp(TL, *yvecL, iLplus - 3, iLplus - 2, iLplus - 1, iLplus, mainval);
354 }
355
356 if (!is_in_closed_range(yV, yL, static_cast<CoolPropDbl>(val))) {
357 return false;
358 } else {
359 iL = iLplus - 1;
360 iV = iVplus - 1;
361 return true;
362 }
363 }
365 void resize(std::size_t N) {
366/* Use X macros to auto-generate the code; each will look something like: T.resize(N); std::fill(T.begin(), T.end(), _HUGE); */
367#define X(name) \
368 name.resize(N); \
369 std::fill(name.begin(), name.end(), _HUGE);
371#undef X
372 };
374 void pack() {
375/* Use X macros to auto-generate the packing code; each will look something like: matrices.insert(std::pair<std::vector<std::vector<double> > >("T", T)); */
376#define X(name) vectors.insert(std::pair<std::string, std::vector<double>>(#name, name));
378#undef X
379 };
380 std::map<std::string, std::vector<double>>::iterator get_vector_iterator(const std::string& name) {
381 auto it = vectors.find(name);
382 if (it == vectors.end()) {
383 throw UnableToLoadError(format("could not find vector %s", name.c_str()));
384 }
385 return it;
386 }
388 void unpack() {
389/* Use X macros to auto-generate the unpacking code; each will look something like: T = get_vector_iterator("T")->second */
390#define X(name) name = get_vector_iterator(#name)->second;
392#undef X
393 N = TL.size();
394 };
395 void deserialize(msgpack::object& deserialized) {
397 deserialized.convert(temp);
398 temp.unpack();
399 if (N != temp.N) {
400 throw ValueError(format("old [%d] and new [%d] sizes don't agree", temp.N, N));
401 } else if (revision > temp.revision) {
402 throw ValueError(format("loaded revision [%d] is older than current revision [%d]", temp.revision, revision));
403 }
404 std::swap(*this, temp); // Swap
405 this->AS = temp.AS; // Reconnect the AbstractState pointer
406 };
407 double evaluate(parameters output, double p_or_T, double Q, std::size_t iL, std::size_t iV) {
408 if (iL <= 2) {
409 iL = 2;
410 } else if (iL + 1 == N) {
411 iL = N - 2;
412 }
413 if (iV <= 2) {
414 iV = 2;
415 } else if (iV + 1 == N) {
416 iV = N - 2;
417 }
418 double logp = log(p_or_T);
419 switch (output) {
420 case iP: {
421 double _logpV = CubicInterp(this->TV, logpV, iV - 2, iV - 1, iV, iV + 1, p_or_T);
422 double _logpL = CubicInterp(this->TL, logpL, iL - 2, iL - 1, iL, iL + 1, p_or_T);
423 return Q * exp(_logpV) + (1 - Q) * exp(_logpL);
424 }
425 case iT: {
426 double TV = CubicInterp(logpV, this->TV, iV - 2, iV - 1, iV, iV + 1, logp);
427 double TL = CubicInterp(logpL, this->TL, iL - 2, iL - 1, iL, iL + 1, logp);
428 return Q * TV + (1 - Q) * TL;
429 }
430 case iSmolar: {
431 double sV = CubicInterp(logpV, smolarV, iV - 2, iV - 1, iV, iV + 1, logp);
432 double sL = CubicInterp(logpL, smolarL, iL - 2, iL - 1, iL, iL + 1, logp);
433 return Q * sV + (1 - Q) * sL;
434 }
435 case iHmolar: {
436 double hV = CubicInterp(logpV, hmolarV, iV - 2, iV - 1, iV, iV + 1, logp);
437 double hL = CubicInterp(logpL, hmolarL, iL - 2, iL - 1, iL, iL + 1, logp);
438 return Q * hV + (1 - Q) * hL;
439 }
440 case iUmolar: {
441 double uV = CubicInterp(logpV, umolarV, iV - 2, iV - 1, iV, iV + 1, logp);
442 double uL = CubicInterp(logpL, umolarL, iL - 2, iL - 1, iL, iL + 1, logp);
443 return Q * uV + (1 - Q) * uL;
444 }
445 case iDmolar: {
446 double rhoV = exp(CubicInterp(logpV, logrhomolarV, iV - 2, iV - 1, iV, iV + 1, logp));
447 double rhoL = exp(CubicInterp(logpL, logrhomolarL, iL - 2, iL - 1, iL, iL + 1, logp));
448 if (!ValidNumber(rhoV)) {
449 throw ValueError("rhoV is invalid");
450 }
451 if (!ValidNumber(rhoL)) {
452 throw ValueError("rhoL is invalid");
453 }
454 return 1 / (Q / rhoV + (1 - Q) / rhoL);
455 }
456 case iconductivity: {
457 double kV = CubicInterp(logpV, condV, iV - 2, iV - 1, iV, iV + 1, logp);
458 double kL = CubicInterp(logpL, condL, iL - 2, iL - 1, iL, iL + 1, logp);
459 if (!ValidNumber(kV)) {
460 throw ValueError("kV is invalid");
461 }
462 if (!ValidNumber(kL)) {
463 throw ValueError("kL is invalid");
464 }
465 return Q * kV + (1 - Q) * kL;
466 }
467 case iviscosity: {
468 double muV = exp(CubicInterp(logpV, logviscV, iV - 2, iV - 1, iV, iV + 1, logp));
469 double muL = exp(CubicInterp(logpL, logviscL, iL - 2, iL - 1, iL, iL + 1, logp));
470 if (!ValidNumber(muV)) {
471 throw ValueError("muV is invalid");
472 }
473 if (!ValidNumber(muL)) {
474 throw ValueError("muL is invalid");
475 }
476 return 1 / (Q / muV + (1 - Q) / muL);
477 }
478 case iCpmolar: {
479 double cpV = CubicInterp(logpV, cpmolarV, iV - 2, iV - 1, iV, iV + 1, logp);
480 double cpL = CubicInterp(logpL, cpmolarL, iL - 2, iL - 1, iL, iL + 1, logp);
481 if (!ValidNumber(cpV)) {
482 throw ValueError("cpV is invalid");
483 }
484 if (!ValidNumber(cpL)) {
485 throw ValueError("cpL is invalid");
486 }
487 return Q * cpV + (1 - Q) * cpL;
488 }
489 case iCvmolar: {
490 double cvV = CubicInterp(logpV, cvmolarV, iV - 2, iV - 1, iV, iV + 1, logp);
491 double cvL = CubicInterp(logpL, cvmolarL, iL - 2, iL - 1, iL, iL + 1, logp);
492 if (!ValidNumber(cvV)) {
493 throw ValueError("cvV is invalid");
494 }
495 if (!ValidNumber(cvL)) {
496 throw ValueError("cvL is invalid");
497 }
498 return Q * cvV + (1 - Q) * cvL;
499 }
500 case ispeed_sound: {
501 double wV = CubicInterp(logpV, speed_soundV, iV - 2, iV - 1, iV, iV + 1, logp);
502 double wL = CubicInterp(logpL, speed_soundL, iL - 2, iL - 1, iL, iL + 1, logp);
503 if (!ValidNumber(wV)) {
504 throw ValueError("wV is invalid");
505 }
506 if (!ValidNumber(wL)) {
507 throw ValueError("wL is invalid");
508 }
509 return Q * wV + (1 - Q) * wL;
510 }
511 default:
512 throw ValueError("Output variable for evaluate is invalid");
513 }
514 };
523 double first_saturation_deriv(parameters Of1, parameters Wrt1, int Q, double val, std::size_t i) {
524 if (i < 2 || i > TL.size() - 2) {
525 throw ValueError(format("Invalid index (%d) to calc_first_saturation_deriv in TabularBackends", i));
526 }
527 std::vector<double>*x, *y;
528 // Connect pointers for each vector
529 switch (Wrt1) {
530 case iT:
531 x = (Q == 0) ? &TL : &TV;
532 break;
533 case iP:
534 x = (Q == 0) ? &pL : &pV;
535 break;
536 default:
537 throw ValueError(format("Key for Wrt1 is invalid in calc_first_saturation_deriv"));
538 }
539 CoolPropDbl factor = 1.0;
540 switch (Of1) {
541 case iT:
542 y = (Q == 0) ? &TL : &TV;
543 break;
544 case iP:
545 y = (Q == 0) ? &pL : &pV;
546 break;
547 case iDmolar:
548 y = (Q == 0) ? &rhomolarL : &rhomolarV;
549 break;
550 case iHmolar:
551 y = (Q == 0) ? &hmolarL : &hmolarV;
552 break;
553 case iSmolar:
554 y = (Q == 0) ? &smolarL : &smolarV;
555 break;
556 case iUmolar:
557 y = (Q == 0) ? &umolarL : &umolarV;
558 break;
559 case iDmass:
560 y = (Q == 0) ? &rhomolarL : &rhomolarV;
561 factor = AS->molar_mass();
562 break;
563 case iHmass:
564 y = (Q == 0) ? &hmolarL : &hmolarV;
565 factor = 1 / AS->molar_mass();
566 break;
567 case iSmass:
568 y = (Q == 0) ? &smolarL : &smolarV;
569 factor = 1 / AS->molar_mass();
570 break;
571 case iUmass:
572 y = (Q == 0) ? &umolarL : &umolarV;
573 factor = 1 / AS->molar_mass();
574 break;
575 default:
576 throw ValueError(format("Key for Of1 is invalid in calc_first_saturation_deriv"));
577 }
578 return CubicInterpFirstDeriv((*x)[i - 2], (*x)[i - 1], (*x)[i], (*x)[i + 1], (*y)[i - 2], (*y)[i - 1], (*y)[i], (*y)[i + 1], val) * factor;
579 };
580 //calc_first_two_phase_deriv(parameters Of, parameters Wrt, parameters Constant);
581};
582
588{
589
590 public:
591 std::size_t Nx, Ny;
593 shared_ptr<CoolProp::AbstractState> AS;
594 std::vector<double> xvec, yvec;
595 std::vector<std::vector<std::size_t>> nearest_neighbor_i, nearest_neighbor_j;
596 bool logx, logy;
597 double xmin, ymin, xmax, ymax;
598
599 virtual void set_limits() = 0;
600
602 : revision(0), xkey(INVALID_PARAMETER), ykey(INVALID_PARAMETER), logx(false), logy(false), xmin(_HUGE), xmax(_HUGE), ymin(_HUGE), ymax(_HUGE) {
603 const int nx_cfg = get_config_int(TABULAR_NX);
604 const int ny_cfg = get_config_int(TABULAR_NY);
605 Nx = (nx_cfg > 1) ? static_cast<std::size_t>(nx_cfg) : 200;
606 Ny = (ny_cfg > 1) ? static_cast<std::size_t>(ny_cfg) : 200;
607 }
608
609/* Use X macros to auto-generate the variables; each will look something like: std::vector< std::vector<double> > T; */
610#define X(name) std::vector<std::vector<double>> name;
612#undef X
614 std::map<std::string, std::vector<std::vector<double>>> matrices;
616 void build(shared_ptr<CoolProp::AbstractState>& AS);
617
618 MSGPACK_DEFINE(revision, matrices, xmin, xmax, ymin, ymax); // write the member variables that you want to pack
620 void resize(std::size_t Nx, std::size_t Ny) {
621/* Use X macros to auto-generate the code; each will look something like: T.resize(Nx, std::vector<double>(Ny, _HUGE)); */
622#define X(name) name.resize(Nx, std::vector<double>(Ny, _HUGE));
624#undef X
626 };
629 if (logx) {
630 xvec = logspace(xmin, xmax, Nx);
631 } else {
632 xvec = linspace(xmin, xmax, Nx);
633 }
634 if (logy) {
635 yvec = logspace(ymin, ymax, Ny);
636 } else {
637 yvec = linspace(ymin, ymax, Ny);
638 }
639 };
642 nearest_neighbor_i.resize(Nx, std::vector<std::size_t>(Ny, std::numeric_limits<std::size_t>::max()));
643 nearest_neighbor_j.resize(Nx, std::vector<std::size_t>(Ny, std::numeric_limits<std::size_t>::max()));
644 for (std::size_t i = 0; i < xvec.size(); ++i) {
645 for (std::size_t j = 0; j < yvec.size(); ++j) {
646 nearest_neighbor_i[i][j] = i;
647 nearest_neighbor_j[i][j] = j;
648 if (!ValidNumber(T[i][j])) {
649 int xoffsets[] = {-1, 1, 0, 0, -1, 1, 1, -1};
650 int yoffsets[] = {0, 0, 1, -1, -1, -1, 1, 1};
651 // Length of offset
652 std::size_t N = sizeof(xoffsets) / sizeof(xoffsets[0]);
653 for (std::size_t k = 0; k < N; ++k) {
654 std::size_t iplus = i + xoffsets[k];
655 std::size_t jplus = j + yoffsets[k];
656 if (0 < iplus && iplus < Nx - 1 && 0 < jplus && jplus < Ny - 1 && ValidNumber(T[iplus][jplus])) {
657 nearest_neighbor_i[i][j] = iplus;
658 nearest_neighbor_j[i][j] = jplus;
659 break;
660 }
661 }
662 }
663 }
664 }
665 };
667 void pack() {
668/* Use X macros to auto-generate the packing code; each will look something like: matrices.insert(std::pair<std::vector<std::vector<double> > >("T", T)); */
669#define X(name) matrices.insert(std::pair<std::string, std::vector<std::vector<double>>>(#name, name));
671#undef X
672 };
673 std::map<std::string, std::vector<std::vector<double>>>::iterator get_matrices_iterator(const std::string& name) {
674 auto it = matrices.find(name);
675 if (it == matrices.end()) {
676 throw UnableToLoadError(format("could not find matrix %s", name.c_str()));
677 }
678 return it;
679 }
681 void unpack() {
682/* Use X macros to auto-generate the unpacking code; each will look something like: T = *(matrices.find("T")).second */
683#define X(name) name = get_matrices_iterator(#name)->second;
685#undef X
686 Nx = T.size();
687 Ny = T[0].size();
690 };
692 bool native_inputs_are_in_range(double x, double y) {
693 double e = 10 * DBL_EPSILON;
694 return x >= xmin - e && x <= xmax + e && y >= ymin - e && y <= ymax + e;
695 }
699 void find_native_nearest_neighbor(double x, double y, std::size_t& i, std::size_t& j) {
700 bisect_vector(xvec, x, i);
701 if (i != Nx - 1) {
702 if (!logx) {
703 if (x > (xvec[i] + xvec[i + 1]) / 2.0) {
704 i++;
705 }
706 } else {
707 if (x > sqrt(xvec[i] * xvec[i + 1])) {
708 i++;
709 }
710 }
711 }
712 bisect_vector(yvec, y, j);
713 if (j != Ny - 1) {
714 if (!logy) {
715 if (y > (yvec[j] + yvec[j + 1]) / 2.0) {
716 j++;
717 }
718 } else {
719 if (y > sqrt(yvec[j] * yvec[j + 1])) {
720 j++;
721 }
722 }
723 }
724 }
726 void find_nearest_neighbor(parameters givenkey, double givenval, parameters otherkey, double otherval, std::size_t& i, std::size_t& j) {
727 if (givenkey == ykey) {
728 bisect_vector(yvec, givenval, j);
729 // This one is problematic because we need to make a slice against the grain in the "matrix"
730 // which requires a slightly different algorithm
731 try {
732 bisect_segmented_vector_slice(get(otherkey), j, otherval, i);
733 } catch (...) {
734 // Now we go for a less intelligent solution, we simply try to find the one that is the closest
735 const std::vector<std::vector<double>>& mat = get(otherkey);
736 double closest_diff = 1e20;
737 std::size_t closest_i = 0;
738 for (std::size_t index = 0; index < mat.size(); ++index) {
739 double diff = std::abs(mat[index][j] - otherval);
740 if (diff < closest_diff) {
741 closest_diff = diff;
742 closest_i = index;
743 }
744 }
745 i = closest_i;
746 }
747 } else if (givenkey == xkey) {
748 bisect_vector(xvec, givenval, i);
749 // This one is fine because we now end up with a vector<double> in the other variable
750 const std::vector<std::vector<double>>& v = get(otherkey);
751 bisect_vector(v[i], otherval, j);
752 }
753 }
756 void find_native_nearest_good_neighbor(double x, double y, std::size_t& i, std::size_t& j) {
757 // Get the node that is closest
759 // Check whether found node is good
760 if (!ValidNumber(T[i][j])) {
761 // If not, find its nearest good neighbor
762 // (nearest good neighbors are precalculated and cached)
763 std::size_t inew = nearest_neighbor_i[i][j];
764 std::size_t jnew = nearest_neighbor_j[i][j];
765 i = inew;
766 j = jnew;
767 }
768 }
771 void find_native_nearest_good_cell(double x, double y, std::size_t& i, std::size_t& j) {
772 bisect_vector(xvec, x, i);
773 bisect_vector(yvec, y, j);
774 }
775 const std::vector<std::vector<double>>& get(parameters key) {
776 switch (key) {
777 case iDmolar:
778 return rhomolar;
779 case iT:
780 return T;
781 case iUmolar:
782 return umolar;
783 case iHmolar:
784 return hmolar;
785 case iSmolar:
786 return smolar;
787 case iP:
788 return p;
789 case iviscosity:
790 return visc;
791 case iconductivity:
792 return cond;
793 default:
794 throw KeyError(format("invalid key"));
795 }
796 }
797};
798
801{
802 public:
804 xkey = iHmolar;
805 ykey = iP;
806 logy = true;
807 logx = false;
808 };
809 void set_limits() override {
810 if (this->AS.get() == nullptr) {
811 throw ValueError("AS is not yet set");
812 }
813 CoolPropDbl Tmin = std::max(AS->Ttriple(), AS->Tmin());
814 // Minimum enthalpy is the saturated liquid enthalpy
815 AS->update(QT_INPUTS, 0, Tmin);
816 xmin = AS->hmolar();
817 ymin = AS->p();
818
819 // Check both the enthalpies at the Tmax isotherm to see whether to use low or high pressure
820 AS->update(DmolarT_INPUTS, 1e-10, 1.499 * AS->Tmax());
821 CoolPropDbl xmax1 = AS->hmolar();
822 AS->update(PT_INPUTS, AS->pmax(), 1.499 * AS->Tmax());
823 CoolPropDbl xmax2 = AS->hmolar();
824 xmax = std::max(xmax1, xmax2);
825
826 ymax = AS->pmax();
827 }
828 void deserialize(msgpack::object& deserialized) {
829 LogPHTable temp;
830 deserialized.convert(temp);
831 temp.unpack();
832 if (Nx != temp.Nx || Ny != temp.Ny) {
833 // Cached file was built at a different grid resolution than the current
834 // TABULAR_NX/TABULAR_NY config requests; force a rebuild via check_tables().
835 throw UnableToLoadError(format("Cached LogPH grid [%dx%d] does not match requested [%dx%d]; will rebuild", temp.Nx, temp.Ny, Nx, Ny));
836 } else if (revision > temp.revision) {
837 throw ValueError(format("loaded revision [%d] is older than current revision [%d]", temp.revision, revision));
838 } else if ((std::abs(xmin) > 1e-10 && std::abs(xmax) > 1e-10)
839 && (std::abs(temp.xmin - xmin) / xmin > 1e-6 || std::abs(temp.xmax - xmax) / xmax > 1e-6)) {
840 throw ValueError(format("Current limits for x [%g,%g] do not agree with loaded limits [%g,%g]", xmin, xmax, temp.xmin, temp.xmax));
841 } else if ((std::abs(ymin) > 1e-10 && std::abs(ymax) > 1e-10)
842 && (std::abs(temp.ymin - ymin) / ymin > 1e-6 || std::abs(temp.ymax - ymax) / ymax > 1e-6)) {
843 throw ValueError(format("Current limits for y [%g,%g] do not agree with loaded limits [%g,%g]", ymin, ymax, temp.ymin, temp.ymax));
844 }
845 std::swap(*this, temp); // Swap
846 this->AS = temp.AS; // Reconnect the AbstractState pointer
847 };
848};
851{
852 public:
854 xkey = iT;
855 ykey = iP;
856 logy = true;
857 logx = false;
858 xmin = _HUGE;
859 ymin = _HUGE;
860 xmax = _HUGE;
861 ymax = _HUGE;
862 };
863 void set_limits() override {
864 if (this->AS.get() == nullptr) {
865 throw ValueError("AS is not yet set");
866 }
867 CoolPropDbl Tmin = std::max(AS->Ttriple(), AS->Tmin());
868 AS->update(QT_INPUTS, 0, Tmin);
869 xmin = Tmin;
870 ymin = AS->p();
871
872 xmax = AS->Tmax() * 1.499;
873 ymax = AS->pmax();
874 }
875 void deserialize(msgpack::object& deserialized) {
876 LogPTTable temp;
877 deserialized.convert(temp);
878 temp.unpack();
879 if (Nx != temp.Nx || Ny != temp.Ny) {
880 // Cached file was built at a different grid resolution than the current
881 // TABULAR_NX/TABULAR_NY config requests; force a rebuild via check_tables().
882 throw UnableToLoadError(format("Cached LogPT grid [%dx%d] does not match requested [%dx%d]; will rebuild", temp.Nx, temp.Ny, Nx, Ny));
883 } else if (revision > temp.revision) {
884 throw ValueError(format("loaded revision [%d] is older than current revision [%d]", temp.revision, revision));
885 } else if ((std::abs(xmin) > 1e-10 && std::abs(xmax) > 1e-10)
886 && (std::abs(temp.xmin - xmin) / xmin > 1e-6 || std::abs(temp.xmax - xmax) / xmax > 1e-6)) {
887 throw ValueError(format("Current limits for x [%g,%g] do not agree with loaded limits [%g,%g]", xmin, xmax, temp.xmin, temp.xmax));
888 } else if ((std::abs(ymin) > 1e-10 && std::abs(ymax) > 1e-10)
889 && (std::abs(temp.ymin - ymin) / ymin > 1e-6 || std::abs(temp.ymax - ymax) / ymax > 1e-6)) {
890 throw ValueError(format("Current limits for y [%g,%g] do not agree with loaded limits [%g,%g]", ymin, ymax, temp.ymin, temp.ymax));
891 }
892 std::swap(*this, temp); // Swap
893 this->AS = temp.AS; // Reconnect the AbstractState pointer
894 };
895};
896
900{
901 private:
902 std::size_t alt_i, alt_j;
903 bool _valid, _has_valid_neighbor;
904
905 public:
907 CellCoeffs() : _valid(false), _has_valid_neighbor(false), dx_dxhat(_HUGE), dy_dyhat(_HUGE), alt_i(9999999), alt_j(9999999) {}
908 std::vector<double> T, rhomolar, hmolar, p, smolar, umolar;
910 const std::vector<double>& get(const parameters params) const {
911 switch (params) {
912 case iT:
913 return T;
914 case iP:
915 return p;
916 case iDmolar:
917 return rhomolar;
918 case iHmolar:
919 return hmolar;
920 case iSmolar:
921 return smolar;
922 case iUmolar:
923 return umolar;
924 default:
925 throw KeyError(format("Invalid key to get() function of CellCoeffs"));
926 }
927 };
929 void set(parameters params, const std::vector<double>& mat) {
930 switch (params) {
931 case iT:
932 T = mat;
933 break;
934 case iP:
935 p = mat;
936 break;
937 case iDmolar:
938 rhomolar = mat;
939 break;
940 case iHmolar:
941 hmolar = mat;
942 break;
943 case iSmolar:
944 smolar = mat;
945 break;
946 case iUmolar:
947 umolar = mat;
948 break;
949 default:
950 throw KeyError(format("Invalid key to set() function of CellCoeffs"));
951 }
952 };
954 [[nodiscard]] bool valid() const {
955 return _valid;
956 };
958 void set_valid() {
959 _valid = true;
960 };
962 void set_invalid() {
963 _valid = false;
964 };
966 void set_alternate(std::size_t i, std::size_t j) {
967 alt_i = i;
968 alt_j = j;
969 _has_valid_neighbor = true;
970 }
972 [[nodiscard]] std::optional<std::pair<std::size_t, std::size_t>> get_alternate() const {
973 if (_has_valid_neighbor) {
974 return std::make_pair(alt_i, alt_j);
975 }
976 return std::nullopt;
977 }
978};
979
982{
983 public:
989 std::vector<std::vector<CellCoeffs>> coeffs_ph, coeffs_pT;
990
993 void write_tables(const std::string& path_to_tables);
995 void load_tables(const std::string& path_to_tables, shared_ptr<CoolProp::AbstractState>& AS);
997 void build_tables(shared_ptr<CoolProp::AbstractState>& AS);
999 void build_coeffs(SinglePhaseGriddedTableData& table, std::vector<std::vector<CellCoeffs>>& coeffs);
1000};
1001
1003{
1004 private:
1005 std::map<std::string, TabularDataSet> data;
1006
1007 public:
1009 std::string path_to_tables(shared_ptr<CoolProp::AbstractState>& AS) {
1010 std::vector<std::string> fluids = AS->fluid_names();
1011 std::vector<CoolPropDbl> fractions = AS->get_mole_fractions();
1012 std::vector<std::string> components;
1013 components.reserve(fluids.size());
1014 for (std::size_t i = 0; i < fluids.size(); ++i) {
1015 components.push_back(format("%s[%0.10Lf]", fluids[i].c_str(), fractions[i]));
1016 }
1017 std::string table_directory = get_home_dir() + "/.CoolProp/Tables/";
1018 std::string alt_table_directory = get_config_string(ALTERNATIVE_TABLES_DIRECTORY);
1019 if (!alt_table_directory.empty()) {
1020 table_directory = alt_table_directory;
1021 }
1022 return table_directory + AS->backend_name() + "(" + strjoin(components, "&") + ")";
1023 }
1025 std::pair<TabularDataSet*, bool> get_set_of_tables(shared_ptr<AbstractState>& AS);
1026};
1027
1035{
1036 protected:
1040 {
1048 std::vector<std::vector<double>> const* z;
1049 std::vector<std::vector<double>> const* dzdx;
1050 std::vector<std::vector<double>> const* dzdy;
1051 std::vector<std::vector<double>> const* d2zdx2;
1052 std::vector<std::vector<double>> const* d2zdxdy;
1053 std::vector<std::vector<double>> const* d2zdy2;
1054 std::vector<CoolPropDbl> mole_fractions;
1055
1056 public:
1057 shared_ptr<CoolProp::AbstractState> AS;
1058 TabularBackend(shared_ptr<CoolProp::AbstractState> AS)
1060 tables_loaded(false),
1062 is_mixture(false),
1064 cached_single_phase_i(std::numeric_limits<std::size_t>::max()),
1065 cached_single_phase_j(std::numeric_limits<std::size_t>::max()),
1066 cached_saturation_iL(std::numeric_limits<std::size_t>::max()),
1067 cached_saturation_iV(std::numeric_limits<std::size_t>::max()),
1068 z(nullptr),
1069 dzdx(nullptr),
1070 dzdy(nullptr),
1071 d2zdx2(nullptr),
1072 d2zdxdy(nullptr),
1073 d2zdy2(nullptr),
1074 AS(std::move(AS)),
1075 dataset(nullptr) {};
1076
1077 // None of the tabular methods are available from the high-level interface
1078 bool available_in_high_level() override {
1079 return false;
1080 }
1081
1082 std::string calc_name() override {
1083 return AS->name();
1084 }
1085 std::vector<std::string> calc_fluid_names() override {
1086 return AS->fluid_names();
1087 }
1088
1090 // Connect the pointers based on the output variable desired
1091 switch (output) {
1092 case iT:
1093 z = &table.T;
1094 dzdx = &table.dTdx;
1095 dzdy = &table.dTdy;
1096 d2zdxdy = &table.d2Tdxdy;
1097 d2zdx2 = &table.d2Tdx2;
1098 d2zdy2 = &table.d2Tdy2;
1099 break;
1100 case iDmolar:
1101 z = &table.rhomolar;
1102 dzdx = &table.drhomolardx;
1103 dzdy = &table.drhomolardy;
1104 d2zdxdy = &table.d2rhomolardxdy;
1105 d2zdx2 = &table.d2rhomolardx2;
1106 d2zdy2 = &table.d2rhomolardy2;
1107 break;
1108 case iSmolar:
1109 z = &table.smolar;
1110 dzdx = &table.dsmolardx;
1111 dzdy = &table.dsmolardy;
1112 d2zdxdy = &table.d2smolardxdy;
1113 d2zdx2 = &table.d2smolardx2;
1114 d2zdy2 = &table.d2smolardy2;
1115 break;
1116 case iHmolar:
1117 z = &table.hmolar;
1118 dzdx = &table.dhmolardx;
1119 dzdy = &table.dhmolardy;
1120 d2zdxdy = &table.d2hmolardxdy;
1121 d2zdx2 = &table.d2hmolardx2;
1122 d2zdy2 = &table.d2hmolardy2;
1123 break;
1124 case iUmolar:
1125 z = &table.umolar;
1126 dzdx = &table.dumolardx;
1127 dzdy = &table.dumolardy;
1128 d2zdxdy = &table.d2umolardxdy;
1129 d2zdx2 = &table.d2umolardx2;
1130 d2zdy2 = &table.d2umolardy2;
1131 break;
1132 case iviscosity:
1133 z = &table.visc;
1134 break;
1135 case iconductivity:
1136 z = &table.cond;
1137 break;
1138 default:
1139 throw ValueError();
1140 }
1141 }
1143
1145 if (p() > p_critical()) {
1146 if (T() > T_critical()) {
1148 } else {
1150 }
1151 } else {
1152 if (T() > T_critical()) {
1154 } else {
1155 // Liquid or vapor
1156 if (rhomolar() > rhomolar_critical()) {
1158 } else {
1160 }
1161 }
1162 }
1163 }
1168 void calc_specify_phase(phases phase_index) override {
1169 imposed_phase_index = phase_index;
1170 };
1171
1174 void calc_unspecify_phase() override {
1176 };
1177
1178 virtual double evaluate_single_phase_phmolar(parameters output, std::size_t i, std::size_t j) = 0;
1179 virtual double evaluate_single_phase_pT(parameters output, std::size_t i, std::size_t j) = 0;
1180 virtual double evaluate_single_phase_phmolar_transport(parameters output, std::size_t i, std::size_t j) = 0;
1181 virtual double evaluate_single_phase_pT_transport(parameters output, std::size_t i, std::size_t j) = 0;
1182
1184 void fast_evaluate(CoolProp::input_pairs input_pair, const double* val1, const double* val2, std::size_t N_inputs,
1185 const CoolProp::parameters* outputs, std::size_t N_outputs, double* out_buffer, std::size_t out_buffer_size, int* status_flags,
1186 std::size_t status_flags_size, CoolProp::phases imposed_phase = CoolProp::iphase_not_imposed) override;
1187 virtual double evaluate_single_phase_phmolar_derivative(parameters output, std::size_t i, std::size_t j, std::size_t Nx, std::size_t Ny) = 0;
1188 virtual double evaluate_single_phase_pT_derivative(parameters output, std::size_t i, std::size_t j, std::size_t Nx, std::size_t Ny) = 0;
1189
1191 virtual void find_native_nearest_good_indices(SinglePhaseGriddedTableData& table, const std::vector<std::vector<CellCoeffs>>& coeffs, double x,
1192 double y, std::size_t& i, std::size_t& j) = 0;
1194 virtual void find_nearest_neighbor(SinglePhaseGriddedTableData& table, const std::vector<std::vector<CellCoeffs>>& coeffs,
1195 const parameters variable1, const double value1, const parameters other, const double otherval, std::size_t& i,
1196 std::size_t& j) = 0;
1198 virtual void invert_single_phase_x(const SinglePhaseGriddedTableData& table, const std::vector<std::vector<CellCoeffs>>& coeffs,
1199 parameters output, double x, double y, std::size_t i, std::size_t j) = 0;
1201 virtual void invert_single_phase_y(const SinglePhaseGriddedTableData& table, const std::vector<std::vector<CellCoeffs>>& coeffs,
1202 parameters output, double x, double y, std::size_t i, std::size_t j) = 0;
1203
1204 phases calc_phase() override {
1205 return _phase;
1206 }
1208 return this->AS->T_critical();
1209 };
1211 return this->AS->Ttriple();
1212 };
1214 return this->AS->p_triple();
1215 };
1217 return this->AS->pmax();
1218 };
1220 return this->AS->Tmax();
1221 };
1223 return this->AS->Tmin();
1224 };
1226 return this->AS->p_critical();
1227 }
1229 return this->AS->rhomolar_critical();
1230 }
1231 bool using_mole_fractions() override {
1232 return true;
1233 }
1234 bool using_mass_fractions() override {
1235 return false;
1236 }
1237 bool using_volu_fractions() override {
1238 return false;
1239 }
1240 void update(CoolProp::input_pairs input_pair, double Value1, double Value2) override;
1241 void set_mole_fractions(const std::vector<CoolPropDbl>& mole_fractions) override {
1242 this->AS->set_mole_fractions(mole_fractions);
1243 };
1244 void set_mass_fractions(const std::vector<CoolPropDbl>& mass_fractions) override {
1245 throw NotImplementedError("set_mass_fractions not implemented for Tabular backends");
1246 };
1247 const std::vector<CoolPropDbl>& get_mole_fractions() override {
1248 return AS->get_mole_fractions();
1249 };
1250 const std::vector<CoolPropDbl> calc_mass_fractions() override {
1251 return AS->get_mass_fractions();
1252 };
1253
1255 return AS->molar_mass();
1256 };
1257
1258 [[nodiscard]] CoolPropDbl calc_saturated_liquid_keyed_output(parameters key) override;
1259 [[nodiscard]] CoolPropDbl calc_saturated_vapor_keyed_output(parameters key) override;
1260
1262 std::string path_to_tables();
1264 void load_tables();
1270 single_phase_logph.pack();
1271 single_phase_logpT.pack();
1272 pure_saturation.pack();
1273 phase_envelope.pack();
1274 }
1276 void write_tables();
1277
1278 CoolPropDbl phase_envelope_sat(const PhaseEnvelopeData& env, parameters output, parameters iInput1, double value1) {
1279 const PhaseEnvelopeData& phase_envelope = dataset->phase_envelope;
1280 CoolPropDbl yL = PhaseEnvelopeRoutines::evaluate(phase_envelope, output, iInput1, value1, cached_saturation_iL);
1281 CoolPropDbl yV = PhaseEnvelopeRoutines::evaluate(phase_envelope, output, iInput1, value1, cached_saturation_iV);
1282 return _Q * yV + (1 - _Q) * yL;
1283 }
1285 this->AS->set_T(_T);
1286 return this->AS->cp0molar();
1287 }
1290 this->AS->set_T(_T);
1291 return this->AS->surface_tension();
1292 this->AS->set_T(_HUGE);
1293 }
1295 CoolPropDbl calc_T() override;
1296 CoolPropDbl calc_rhomolar() override;
1297 CoolPropDbl calc_hmolar() override;
1298 CoolPropDbl calc_smolar() override;
1299 CoolPropDbl calc_umolar() override;
1300 CoolPropDbl calc_cpmolar() override;
1301 CoolPropDbl calc_cvmolar() override;
1302 CoolPropDbl calc_viscosity() override;
1303 CoolPropDbl calc_conductivity() override;
1305 CoolPropDbl calc_speed_sound() override;
1311
1314
1316 if (!tables_loaded) {
1317 try {
1319 load_tables();
1320 // Set the flag saying tables have been successfully loaded
1321 tables_loaded = true;
1322 } catch (CoolProp::UnableToLoadError& e) {
1323 if (get_debug_level() > 0) {
1324 std::cout << format("Table loading failed with error: %s\n", e.what());
1325 }
1327 std::string table_path = path_to_tables();
1328 double directory_size_in_GB = CalculateDirSize(table_path) / POW3(1024.0);
1329 double allowed_size_in_GB = get_config_double(MAXIMUM_TABLE_DIRECTORY_SIZE_IN_GB);
1330 if (get_debug_level() > 0) {
1331 std::cout << "Tabular directory size is " << directory_size_in_GB << " GB\n";
1332 }
1333 if (directory_size_in_GB > 1.5 * allowed_size_in_GB) {
1334 throw DirectorySizeError(
1335 format("Maximum allowed tabular directory size is %g GB, you have exceeded 1.5 times this limit", allowed_size_in_GB));
1336 } else if (directory_size_in_GB > allowed_size_in_GB) {
1337 set_warning_string(format("Maximum allowed tabular directory size is %g GB, you have exceeded this limit", allowed_size_in_GB));
1338 }
1340 dataset->build_tables(this->AS);
1341 pack_matrices();
1342 write_tables();
1344 load_tables();
1345 // Set the flag saying tables have been successfully loaded
1346 tables_loaded = true;
1347 }
1348 }
1349 };
1350};
1351
1352} /* namespace CoolProp*/
1353
1354#endif