2#if !defined(NO_TABULAR_BACKENDS)
14static const double Ainv_data[16 * 16] = {
15 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -3, 3, 0, 0, -2,
16 -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, -2, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
17 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -3, 3, 0, 0, -2, -1, 0,
18 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, -2, 0, 0, 1, 1, 0, 0, -3, 0, 3, 0, 0, 0, 0, 0, -2, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
19 -3, 0, 3, 0, 0, 0, 0, 0, -2, 0, -1, 0, 9, -9, -9, 9, 6, 3, -6, -3, 6, -6, 3, -3, 4, 2, 2, 1, -6, 6, 6, -6, -3, -3, 3, 3, -4,
20 4, -2, 2, -2, -2, -1, -1, 2, 0, -2, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, -2, 0, 0, 0, 0, 0, 1, 0,
21 1, 0, -6, 6, 6, -6, -4, -2, 4, 2, -3, 3, -3, 3, -2, -1, -2, -1, 4, -4, -4, 4, 2, 2, -2, -2, 2, -2, 2, -2, 1, 1, 1, 1};
22static Eigen::Matrix<double, 16, 16> Ainv(Ainv_data);
35void load_table(
T& table,
const std::string& path_to_tables,
const std::string& filename) {
38 std::string path_to_table = path_to_tables +
"/" + filename;
40 std::cout <<
format(
"Loading table: %s", path_to_table.c_str()) <<
'\n';
42 std::vector<char> raw;
46 std::string err =
format(
"Unable to load file %s", path_to_table.c_str());
48 std::cout <<
"err:" << err <<
'\n';
52 std::vector<unsigned char> newBuffer(raw.size() * 5);
53 auto newBufferSize =
static_cast<uLong
>(newBuffer.size());
54 auto rawBufferSize =
static_cast<mz_ulong
>(raw.size());
57 code = uncompress((
unsigned char*)(&(newBuffer[0])), &newBufferSize, (
unsigned char*)(&(raw[0])), rawBufferSize);
58 if (code == Z_BUF_ERROR) {
60 newBuffer.resize(newBuffer.size() * 5);
61 newBufferSize =
static_cast<uLong
>(newBuffer.size());
62 }
else if (code != 0) {
63 std::string err =
format(
"Unable to uncompress file %s with miniz code %d", path_to_table.c_str(), code);
65 std::cout <<
"uncompress err:" << err <<
'\n';
71 std::vector<char> charbuffer(newBuffer.begin(), newBuffer.begin() + newBufferSize);
73 msgpack::unpacked msg;
74 msgpack::unpack(msg, &(charbuffer[0]), charbuffer.size());
75 msgpack::object deserialized = msg.get();
78 table.deserialize(deserialized);
81 std::cout <<
format(
"Loaded table: %s in %g sec.", path_to_table.c_str(), (toc - tic) / CLOCKS_PER_SEC) <<
'\n';
83 }
catch (std::exception& e) {
84 std::string err =
format(
"Unable to msgpack deserialize %s; err: %s", path_to_table.c_str(), e.what());
86 std::cout <<
"err: " << err <<
'\n';
92void write_table(
const T& table,
const std::string& path_to_tables,
const std::string& name) {
93 msgpack::sbuffer sbuf;
94 msgpack::pack(sbuf, table);
95 std::string tabPath = std::string(path_to_tables +
"/" + name +
".bin");
96 std::string zPath = tabPath +
".z";
97 std::vector<char> buffer(sbuf.size());
98 auto outSize =
static_cast<uLong
>(buffer.size());
99 compress((
unsigned char*)(&(buffer[0])), &outSize, (
unsigned char*)(sbuf.data()),
static_cast<mz_ulong
>(sbuf.size()));
100 std::ofstream ofs2(zPath.c_str(), std::ofstream::binary);
101 ofs2.write(&buffer[0], outSize);
105 std::ofstream ofs(tabPath.c_str(), std::ofstream::binary);
106 ofs.write(sbuf.data(), sbuf.size());
115 std::cout <<
format(
"***********************************************\n");
116 std::cout <<
format(
" Saturation Table (%s) \n",
AS->name().c_str());
117 std::cout <<
format(
"***********************************************\n");
126 CoolPropDbl p = NAN, pmin = p_triple, pmax = 0.9999 *
AS->p_critical();
127 for (std::size_t i = 0; i <
N - 1; ++i) {
132 p = exp(log(pmin) + (log(pmax) - log(pmin)) / (
N - 1) * i);
139 rhomolarL[i] =
AS->rhomolar();
140 hmolarL[i] =
AS->hmolar();
141 smolarL[i] =
AS->smolar();
142 umolarL[i] =
AS->umolar();
144 logrhomolarL[i] = log(rhomolarL[i]);
145 cpmolarL[i] =
AS->cpmolar();
146 cvmolarL[i] =
AS->cvmolar();
147 speed_soundL[i] =
AS->speed_sound();
148 }
catch (std::exception& e) {
151 std::cout <<
" " << e.what() <<
'\n';
157 viscL[i] =
AS->viscosity();
158 condL[i] =
AS->conductivity();
159 logviscL[i] = log(viscL[i]);
160 }
catch (std::exception& e) {
162 std::cout <<
" " << e.what() <<
'\n';
170 rhomolarV[i] =
AS->rhomolar();
171 hmolarV[i] =
AS->hmolar();
172 smolarV[i] =
AS->smolar();
173 umolarV[i] =
AS->umolar();
175 logrhomolarV[i] = log(rhomolarV[i]);
176 cpmolarV[i] =
AS->cpmolar();
177 cvmolarV[i] =
AS->cvmolar();
178 speed_soundV[i] =
AS->speed_sound();
179 }
catch (std::exception& e) {
182 std::cout <<
" " << e.what() <<
'\n';
188 viscV[i] =
AS->viscosity();
189 condV[i] =
AS->conductivity();
190 logviscV[i] = log(viscV[i]);
191 }
catch (std::exception& e) {
193 std::cout <<
" " << e.what() <<
'\n';
202 std::size_t i =
N - 1;
206 rhomolarV[i] =
AS->rhomolar();
207 hmolarV[i] =
AS->hmolar();
208 smolarV[i] =
AS->smolar();
209 umolarV[i] =
AS->umolar();
213 rhomolarL[i] =
AS->rhomolar();
214 hmolarL[i] =
AS->hmolar();
215 smolarL[i] =
AS->smolar();
216 umolarL[i] =
AS->umolar();
218 logpV[i] = log(
AS->p());
219 logrhomolarV[i] = log(rhomolarV[i]);
221 logpL[i] = log(
AS->p());
222 logrhomolarL[i] = log(rhomolarL[i]);
232 std::cout <<
format(
"***********************************************\n");
233 std::cout <<
format(
" Single-Phase Table (%s) \n",
strjoin(AS->fluid_names(),
"&").c_str());
234 std::cout <<
format(
"***********************************************\n");
239 for (std::size_t i = 0; i < Nx; ++i) {
243 x = exp(log(xmin) + (log(xmax) - log(xmin)) / (Nx - 1) * i);
246 x = xmin + (xmax - xmin) / (Nx - 1) * i;
249 for (std::size_t j = 0; j < Ny; ++j) {
253 y = exp(log(ymin) + (log(ymax / ymin)) / (Ny - 1) * j);
256 y = ymin + (ymax - ymin) / (Ny - 1) * j;
261 std::cout <<
"x: " << x <<
" y: " << y <<
'\n';
272 AS->update(input_pair, v1, v2);
276 }
catch (std::exception& e) {
279 std::cout <<
" " << e.what() <<
'\n';
287 std::cout <<
" 2Phase" <<
'\n';
297 rhomolar[i][j] = AS->rhomolar();
298 hmolar[i][j] = AS->hmolar();
299 smolar[i][j] = AS->smolar();
300 umolar[i][j] = AS->umolar();
306 visc[i][j] = AS->viscosity();
307 cond[i][j] = AS->conductivity();
308 }
catch (std::exception&) {
315 dTdx[i][j] = AS->first_partial_deriv(
iT, xkey, ykey);
316 dTdy[i][j] = AS->first_partial_deriv(
iT, ykey, xkey);
317 dpdx[i][j] = AS->first_partial_deriv(
iP, xkey, ykey);
318 dpdy[i][j] = AS->first_partial_deriv(
iP, ykey, xkey);
319 drhomolardx[i][j] = AS->first_partial_deriv(
iDmolar, xkey, ykey);
320 drhomolardy[i][j] = AS->first_partial_deriv(
iDmolar, ykey, xkey);
321 dhmolardx[i][j] = AS->first_partial_deriv(
iHmolar, xkey, ykey);
322 dhmolardy[i][j] = AS->first_partial_deriv(
iHmolar, ykey, xkey);
323 dsmolardx[i][j] = AS->first_partial_deriv(
iSmolar, xkey, ykey);
324 dsmolardy[i][j] = AS->first_partial_deriv(
iSmolar, ykey, xkey);
325 dumolardx[i][j] = AS->first_partial_deriv(
iUmolar, xkey, ykey);
326 dumolardy[i][j] = AS->first_partial_deriv(
iUmolar, ykey, xkey);
331 d2Tdx2[i][j] = AS->second_partial_deriv(
iT, xkey, ykey, xkey, ykey);
332 d2Tdxdy[i][j] = AS->second_partial_deriv(
iT, xkey, ykey, ykey, xkey);
333 d2Tdy2[i][j] = AS->second_partial_deriv(
iT, ykey, xkey, ykey, xkey);
334 d2pdx2[i][j] = AS->second_partial_deriv(
iP, xkey, ykey, xkey, ykey);
335 d2pdxdy[i][j] = AS->second_partial_deriv(
iP, xkey, ykey, ykey, xkey);
336 d2pdy2[i][j] = AS->second_partial_deriv(
iP, ykey, xkey, ykey, xkey);
337 d2rhomolardx2[i][j] = AS->second_partial_deriv(
iDmolar, xkey, ykey, xkey, ykey);
338 d2rhomolardxdy[i][j] = AS->second_partial_deriv(
iDmolar, xkey, ykey, ykey, xkey);
339 d2rhomolardy2[i][j] = AS->second_partial_deriv(
iDmolar, ykey, xkey, ykey, xkey);
340 d2hmolardx2[i][j] = AS->second_partial_deriv(
iHmolar, xkey, ykey, xkey, ykey);
341 d2hmolardxdy[i][j] = AS->second_partial_deriv(
iHmolar, xkey, ykey, ykey, xkey);
342 d2hmolardy2[i][j] = AS->second_partial_deriv(
iHmolar, ykey, xkey, ykey, xkey);
343 d2smolardx2[i][j] = AS->second_partial_deriv(
iSmolar, xkey, ykey, xkey, ykey);
344 d2smolardxdy[i][j] = AS->second_partial_deriv(
iSmolar, xkey, ykey, ykey, xkey);
345 d2smolardy2[i][j] = AS->second_partial_deriv(
iSmolar, ykey, xkey, ykey, xkey);
346 d2umolardx2[i][j] = AS->second_partial_deriv(
iUmolar, xkey, ykey, xkey, ykey);
347 d2umolardxdy[i][j] = AS->second_partial_deriv(
iUmolar, xkey, ykey, ykey, xkey);
348 d2umolardy2[i][j] = AS->second_partial_deriv(
iUmolar, ykey, xkey, ykey, xkey);
353 std::vector<std::string> fluids = AS->fluid_names();
354 std::vector<CoolPropDbl> fractions = AS->get_mole_fractions();
355 std::vector<std::string> components;
356 components.reserve(fluids.size());
357 for (std::size_t i = 0; i < fluids.size(); ++i) {
358 components.push_back(
format(
"%s[%0.10Lf]", fluids[i].c_str(), fractions[i]));
360 std::string table_directory =
get_home_dir() +
"/.CoolProp/Tables/";
361 std::string alt_table_directory =
get_config_string(ALTERNATIVE_TABLES_DIRECTORY);
362 if (!alt_table_directory.empty()) {
363 table_directory = alt_table_directory;
365 return table_directory + AS->backend_name() +
"(" +
strjoin(components,
"&") +
")";
369 std::string path_to_tables = this->path_to_tables();
376 write_table(single_phase_logph, path_to_tables,
"single_phase_logph");
377 write_table(single_phase_logpT, path_to_tables,
"single_phase_logpT");
378 write_table(pure_saturation, path_to_tables,
"pure_saturation");
379 write_table(phase_envelope, path_to_tables,
"phase_envelope");
388 std::cout <<
"Tables loaded" <<
'\n';
398 return phase_envelope_sat(phase_envelope, key,
iP, _p) * factor;
400 return pure_saturation.
evaluate(key, _p, 1, cached_saturation_iL, cached_saturation_iV) * factor;
409 return phase_envelope_sat(phase_envelope, key,
iP, _p) * factor;
411 return pure_saturation.
evaluate(key, _p, 0, cached_saturation_iL, cached_saturation_iV) * factor;
417 if (using_single_phase_table) {
421 return phase_envelope_sat(phase_envelope,
iP,
iT, _T);
430 if (using_single_phase_table) {
431 switch (selected_table) {
432 case SELECTED_PH_TABLE:
433 return evaluate_single_phase_phmolar(
iT, cached_single_phase_i, cached_single_phase_j);
434 case SELECTED_PT_TABLE:
436 case SELECTED_NO_TABLE:
442 return phase_envelope_sat(phase_envelope,
iT,
iP, _p);
447 return pure_saturation.
evaluate(
iT, _p, _Q, cached_saturation_iL, cached_saturation_iV);
455 if (using_single_phase_table) {
456 switch (selected_table) {
457 case SELECTED_PH_TABLE:
458 return evaluate_single_phase_phmolar(
iDmolar, cached_single_phase_i, cached_single_phase_j);
459 case SELECTED_PT_TABLE:
460 return evaluate_single_phase_pT(
iDmolar, cached_single_phase_i, cached_single_phase_j);
461 case SELECTED_NO_TABLE:
467 return phase_envelope_sat(phase_envelope,
iDmolar,
iP, _p);
469 return pure_saturation.
evaluate(
iDmolar, _p, _Q, cached_saturation_iL, cached_saturation_iV);
476 if (using_single_phase_table) {
477 switch (selected_table) {
478 case SELECTED_PH_TABLE:
480 case SELECTED_PT_TABLE:
481 return evaluate_single_phase_pT(
iHmolar, cached_single_phase_i, cached_single_phase_j);
482 case SELECTED_NO_TABLE:
488 return phase_envelope_sat(phase_envelope,
iHmolar,
iP, _p);
490 return pure_saturation.
evaluate(
iHmolar, _p, _Q, cached_saturation_iL, cached_saturation_iV);
497 if (using_single_phase_table) {
498 switch (selected_table) {
499 case SELECTED_PH_TABLE:
500 return evaluate_single_phase_phmolar(
iSmolar, cached_single_phase_i, cached_single_phase_j);
501 case SELECTED_PT_TABLE:
502 return evaluate_single_phase_pT(
iSmolar, cached_single_phase_i, cached_single_phase_j);
503 case SELECTED_NO_TABLE:
509 return phase_envelope_sat(phase_envelope,
iSmolar,
iP, _p);
511 return pure_saturation.
evaluate(
iSmolar, _p, _Q, cached_saturation_iL, cached_saturation_iV);
518 if (using_single_phase_table) {
519 switch (selected_table) {
520 case SELECTED_PH_TABLE:
521 return evaluate_single_phase_phmolar(
iUmolar, cached_single_phase_i, cached_single_phase_j);
522 case SELECTED_PT_TABLE:
523 return evaluate_single_phase_pT(
iUmolar, cached_single_phase_i, cached_single_phase_j);
524 case SELECTED_NO_TABLE:
533 return hmolar - _p / rhomolar;
535 return pure_saturation.
evaluate(
iUmolar, _p, _Q, cached_saturation_iL, cached_saturation_iV);
542 if (using_single_phase_table) {
546 return phase_envelope_sat(phase_envelope,
iCpmolar,
iP, _p);
548 return pure_saturation.
evaluate(
iCpmolar, _p, _Q, cached_saturation_iL, cached_saturation_iV);
555 if (using_single_phase_table) {
559 return phase_envelope_sat(phase_envelope,
iCvmolar,
iP, _p);
561 return pure_saturation.
evaluate(
iCvmolar, _p, _Q, cached_saturation_iL, cached_saturation_iV);
569 if (using_single_phase_table) {
570 switch (selected_table) {
571 case SELECTED_PH_TABLE:
572 return evaluate_single_phase_phmolar_transport(
iviscosity, cached_single_phase_i, cached_single_phase_j);
573 case SELECTED_PT_TABLE:
574 return evaluate_single_phase_pT_transport(
iviscosity, cached_single_phase_i, cached_single_phase_j);
575 case SELECTED_NO_TABLE:
581 return phase_envelope_sat(phase_envelope,
iviscosity,
iP, _p);
583 return pure_saturation.
evaluate(
iviscosity, _p, _Q, cached_saturation_iL, cached_saturation_iV);
590 if (using_single_phase_table) {
591 switch (selected_table) {
592 case SELECTED_PH_TABLE:
593 return evaluate_single_phase_phmolar_transport(
iconductivity, cached_single_phase_i, cached_single_phase_j);
594 case SELECTED_PT_TABLE:
595 return evaluate_single_phase_pT_transport(
iconductivity, cached_single_phase_i, cached_single_phase_j);
596 case SELECTED_NO_TABLE:
611 if (using_single_phase_table) {
612 return sqrt(1 / molar_mass() * cpmolar() / cvmolar() * first_partial_deriv(
iP,
iDmolar,
iT));
617 return pure_saturation.
evaluate(
ispeed_sound, _p, _Q, cached_saturation_iL, cached_saturation_iV);
622 if (using_single_phase_table) {
623 CoolPropDbl dOf_dx = NAN, dOf_dy = NAN, dWrt_dx = NAN, dWrt_dy = NAN, dConstant_dx = NAN, dConstant_dy = NAN;
626 double Of_conversion_factor = 1.0, Wrt_conversion_factor = 1.0, Constant_conversion_factor = 1.0, MM = AS->molar_mass();
631 switch (selected_table) {
632 case SELECTED_PH_TABLE: {
633 dOf_dx = evaluate_single_phase_phmolar_derivative(Of, cached_single_phase_i, cached_single_phase_j, 1, 0);
634 dOf_dy = evaluate_single_phase_phmolar_derivative(Of, cached_single_phase_i, cached_single_phase_j, 0, 1);
635 dWrt_dx = evaluate_single_phase_phmolar_derivative(Wrt, cached_single_phase_i, cached_single_phase_j, 1, 0);
636 dWrt_dy = evaluate_single_phase_phmolar_derivative(Wrt, cached_single_phase_i, cached_single_phase_j, 0, 1);
637 dConstant_dx = evaluate_single_phase_phmolar_derivative(Constant, cached_single_phase_i, cached_single_phase_j, 1, 0);
638 dConstant_dy = evaluate_single_phase_phmolar_derivative(Constant, cached_single_phase_i, cached_single_phase_j, 0, 1);
641 case SELECTED_PT_TABLE: {
642 dOf_dx = evaluate_single_phase_pT_derivative(Of, cached_single_phase_i, cached_single_phase_j, 1, 0);
643 dOf_dy = evaluate_single_phase_pT_derivative(Of, cached_single_phase_i, cached_single_phase_j, 0, 1);
644 dWrt_dx = evaluate_single_phase_pT_derivative(Wrt, cached_single_phase_i, cached_single_phase_j, 1, 0);
645 dWrt_dy = evaluate_single_phase_pT_derivative(Wrt, cached_single_phase_i, cached_single_phase_j, 0, 1);
646 dConstant_dx = evaluate_single_phase_pT_derivative(Constant, cached_single_phase_i, cached_single_phase_j, 1, 0);
647 dConstant_dy = evaluate_single_phase_pT_derivative(Constant, cached_single_phase_i, cached_single_phase_j, 0, 1);
650 case SELECTED_NO_TABLE:
653 double val = (dOf_dx * dConstant_dy - dOf_dy * dConstant_dx) / (dWrt_dx * dConstant_dy - dWrt_dy * dConstant_dx);
654 return val * Of_conversion_factor / Wrt_conversion_factor;
656 throw ValueError(
format(
"Inputs [rho: %g mol/m3, T: %g K, p: %g Pa] are two-phase; cannot use single-phase derivatives", _rhomolar, _T, _p));
662 if (AS->get_mole_fractions().size() > 1) {
663 throw ValueError(
"calc_first_saturation_deriv not available for mixtures");
665 if (std::abs(_Q) < 1e-6) {
667 }
else if (std::abs(_Q - 1) < 1e-6) {
670 throw ValueError(
format(
"Quality [%Lg] must be either 0 or 1 to within 1 ppm", _Q));
680 return -
POW2(rhomolar()) * (1 / rhoV - 1 / rhoL) / (hV - hL);
695 CoolPropDbl dxdp_h = (Q() * dhV_dp + (1 - Q()) * dhL_dp) / (hL - hV);
696 CoolPropDbl dvdp_h = dvL_dp + dxdp_h * (1 / rhoV - 1 / rhoL) + Q() * (dvV_dp - dvL_dp);
697 return -
POW2(rhomolar()) * dvdp_h;
701 throw ValueError(
"These inputs are not supported to calc_first_two_phase_deriv");
709 bool drho_dh__p =
false;
710 bool drho_dp__h =
false;
711 bool rho_spline =
false;
715 if (_drho_spline_dh__constp)
return _drho_spline_dh__constp;
720 if (_drho_spline_dp__consth)
return _drho_spline_dp__consth;
722 return first_two_phase_deriv_splined(
iDmolar,
iP,
iHmolar, x_end) * molar_mass();
727 if (_rho_spline)
return _rho_spline;
731 throw ValueError(
"These inputs are not supported to calc_first_two_phase_deriv");
735 throw ValueError(
format(
"Q [%g] is greater than x_end [%Lg]", _Q, x_end).c_str());
765 CoolPropDbl TE = pure_saturation.
evaluate(
iT, _p, x_end, cached_saturation_iL, cached_saturation_iV);
780 CoolPropDbl Abracket = (2 * dL - 2 * dE + Delta_end * (drho_dh_liq__constp + drho_dh_end__constp));
782 CoolPropDbl b = 3 /
POW2(Delta_end) * (-dL + dE) - 1 / Delta_end * (drho_dh_end__constp + 2 * drho_dh_liq__constp);
787 _rho_spline = a *
POW3(Delta) + b *
POW2(Delta) + c * Delta + d;
788 _drho_spline_dh__constp = 3 * a *
POW2(Delta) + 2 * b * Delta + c;
789 if (rho_spline)
return _rho_spline;
790 if (drho_dh__p)
return _drho_spline_dh__constp;
802 CoolPropDbl drho_dp_end =
POW2(dE) * (x_end /
POW2(dV) * drhoV_dp_sat + (1 - x_end) /
POW2(dL) * drhoL_dp_sat);
816 CoolPropDbl d_Delta_end_dp__consth = x_end * (dhV_dp_sat - dhL_dp_sat);
820 CoolPropDbl d_Abracket_dp_consth = (2 * drhoL_dp_sat - 2 * drho_dp_end + Delta_end * (d2rhodhdp_liq + d2rhodhdp_end)
821 + d_Delta_end_dp__consth * (drho_dh_liq__constp + drho_dh_end__constp));
822 CoolPropDbl da_dp = 1 /
POW3(Delta_end) * d_Abracket_dp_consth + Abracket * (-3 /
POW4(Delta_end) * d_Delta_end_dp__consth);
823 CoolPropDbl db_dp = -6 /
POW3(Delta_end) * d_Delta_end_dp__consth * (dE - dL) + 3 /
POW2(Delta_end) * (drho_dp_end - drhoL_dp_sat)
824 + (1 /
POW2(Delta_end) * d_Delta_end_dp__consth) * (drho_dh_end__constp + 2 * drho_dh_liq__constp)
825 - (1 / Delta_end) * (d2rhodhdp_end + 2 * d2rhodhdp_liq);
829 _drho_spline_dp__consth =
830 (3 * a *
POW2(Delta) + 2 * b * Delta + c) * d_Delta_dp__consth +
POW3(Delta) * da_dp +
POW2(Delta) * db_dp + Delta * dc_dp + dd_dp;
831 if (drho_dp__h)
return _drho_spline_dp__consth;
833 throw ValueError(
"Something went wrong in TabularBackend::calc_first_two_phase_deriv_splined");
840enum FastEvalOutputKind :
unsigned char
856 return fe_single_phase;
861 return fe_unsupported;
868 std::size_t out_buffer_size,
int* status_flags, std::size_t status_flags_size,
872 if (N_outputs != 0 && N_inputs > std::numeric_limits<std::size_t>::max() / N_outputs) {
873 throw ValueError(
format(
"fast_evaluate: N_inputs * N_outputs would overflow size_t (N_inputs=%zu, N_outputs=%zu)", N_inputs, N_outputs));
875 const std::size_t required_out = N_inputs * N_outputs;
876 if (out_buffer_size < required_out) {
877 throw ValueError(
format(
"fast_evaluate: out_buffer_size=%zu < required %zu (N_inputs * N_outputs)", out_buffer_size, required_out));
879 if (status_flags_size < N_inputs) {
880 throw ValueError(
format(
"fast_evaluate: status_flags_size=%zu < required %zu (N_inputs)", status_flags_size, N_inputs));
882 if (N_inputs == 0)
return;
885 if (val1 ==
nullptr || val2 ==
nullptr || outputs ==
nullptr || out_buffer ==
nullptr || status_flags ==
nullptr) {
886 throw ValueError(
"fast_evaluate: null pointer argument");
888 if (N_outputs == 0) {
889 for (std::size_t k = 0; k < N_inputs; ++k)
895 const bool is_pT = (input_pair ==
PT_INPUTS);
896 if (!is_ph && !is_pT) {
902 constexpr std::size_t MAX_FE_OUTPUTS = 64;
903 if (N_outputs > MAX_FE_OUTPUTS) {
904 throw ValueError(
format(
"fast_evaluate: N_outputs=%zu exceeds compile-time limit %zu", N_outputs, MAX_FE_OUTPUTS));
906 FastEvalOutputKind output_kinds[MAX_FE_OUTPUTS];
907 for (std::size_t o = 0; o < N_outputs; ++o) {
908 output_kinds[o] = classify_fast_eval_output(outputs[o]);
909 if (output_kinds[o] == fe_unsupported) {
923 const std::vector<std::vector<CellCoeffs>>& coeffs = is_ph ? dataset->coeffs_ph : dataset->coeffs_pT;
929 const phases saved_imposed_phase = imposed_phase_index;
931 imposed_phase_index = imposed_phase;
934 const double NaN = std::numeric_limits<double>::quiet_NaN();
936 auto fill_nan_row = [&](std::size_t k) {
937 for (std::size_t o = 0; o < N_outputs; ++o) {
938 out_buffer[k * N_outputs + o] =
NaN;
942 for (std::size_t k = 0; k < N_inputs; ++k) {
946 const double v1 = val1[k];
947 const double v2 = val2[k];
948 const double x = is_ph ? v1 : v2;
949 const double y = is_ph ? v2 : v1;
957 std::size_t i = 0, j = 0;
959 find_native_nearest_good_indices(table, coeffs, x, y, i, j);
960 }
catch (
const std::exception&) {
980 bool eval_failed =
false;
981 for (std::size_t o = 0; o < N_outputs; ++o) {
983 const FastEvalOutputKind kind = output_kinds[o];
986 if (kind == fe_transport) {
987 val = is_ph ? evaluate_single_phase_phmolar_transport(out_key, i, j) : evaluate_single_phase_pT_transport(out_key, i, j);
993 val = is_ph ? v2 : v1;
994 }
else if (is_ph && out_key ==
iHmolar) {
996 }
else if (is_pT && out_key ==
iT) {
999 val = is_ph ? evaluate_single_phase_phmolar(out_key, i, j) : evaluate_single_phase_pT(out_key, i, j);
1002 out_buffer[k * N_outputs + o] = val;
1003 }
catch (
const std::exception&) {
1005 out_buffer[k * N_outputs + o] =
NaN;
1021 imposed_phase_index = saved_imposed_phase;
1036 mass_to_molar_inputs(input_pair, ld_value1, ld_value2);
1044 cached_single_phase_i = std::numeric_limits<std::size_t>::max();
1045 cached_single_phase_j = std::numeric_limits<std::size_t>::max();
1046 cached_saturation_iL = std::numeric_limits<std::size_t>::max();
1047 cached_saturation_iV = std::numeric_limits<std::size_t>::max();
1057 switch (input_pair) {
1063 using_single_phase_table =
false;
1065 std::cout <<
"inputs are not in range";
1069 using_single_phase_table =
true;
1070 std::size_t iL = std::numeric_limits<std::size_t>::max(), iV = std::numeric_limits<std::size_t>::max(), iclosest = 0;
1073 bool is_two_phase =
false;
1086 using_single_phase_table =
false;
1087 _Q = (
static_cast<double>(_hmolar) - hL) / (hV - hL);
1090 format(
"vapor quality is not in (0,1) for hmolar: %g p: %g, hL: %g hV: %g ",
static_cast<double>(_hmolar), _p, hL, hV));
1092 cached_saturation_iL = iL;
1093 cached_saturation_iV = iV;
1097 selected_table = SELECTED_PH_TABLE;
1099 find_native_nearest_good_indices(single_phase_logph, dataset->coeffs_ph, _hmolar, _p, cached_single_phase_i,
1100 cached_single_phase_j);
1102 recalculate_singlephase_phase();
1112 using_single_phase_table =
false;
1114 std::cout <<
"inputs are not in range";
1116 throw ValueError(
format(
"inputs are not in range, p=%g Pa, T=%g K", _p, _T));
1118 using_single_phase_table =
true;
1119 std::size_t iL = 0, iV = 0, iclosest = 0;
1122 bool is_two_phase =
false;
1130 is_two_phase = pure_saturation.
is_inside(
iP, _p,
iT, _T, iL, iV, TL, TV);
1134 using_single_phase_table =
false;
1137 selected_table = SELECTED_PT_TABLE;
1139 find_native_nearest_good_indices(single_phase_logpT, dataset->coeffs_pT, _T, _p, cached_single_phase_i, cached_single_phase_j);
1142 double rhoc = rhomolar_critical();
1148 auto ensure_bumped_cell_valid = [&](
const char* context) {
1149 const auto& cell = dataset->coeffs_pT[cached_single_phase_i][cached_single_phase_j];
1150 if (!cell.valid()) {
1151 if (
auto alt = cell.get_alternate()) {
1152 auto [ai, aj] = *alt;
1153 cached_single_phase_i = ai;
1154 cached_single_phase_j = aj;
1157 format(
"Imposed phase %s: bumped cell (%zu, %zu) has no bicubic coefficients and no good neighbour "
1158 "(p=%g Pa, T=%g K)",
1159 context, cached_single_phase_i, cached_single_phase_j, _p,
static_cast<double>(_T)));
1163 if (imposed_phase_index ==
iphase_liquid && cached_single_phase_i > 0) {
1165 if (_p < this->AS->p_critical()) {
1166 while (cached_single_phase_i > 0
1167 && single_phase_logpT.rhomolar[cached_single_phase_i + 1][cached_single_phase_j] < rhoc) {
1169 cached_single_phase_i--;
1171 ensure_bumped_cell_valid(
"liquid");
1172 double rho = evaluate_single_phase_pT(
iDmolar, cached_single_phase_i, cached_single_phase_j);
1181 && cached_single_phase_i > 0) {
1184 if (_p < this->AS->p_critical()) {
1185 while (cached_single_phase_i > 0
1186 && single_phase_logpT.rhomolar[cached_single_phase_i][cached_single_phase_j + 1] > rhoc) {
1188 cached_single_phase_i++;
1190 ensure_bumped_cell_valid(
"gas");
1191 double rho = evaluate_single_phase_pT(
iDmolar, cached_single_phase_i, cached_single_phase_j);
1205 if (_p < this->AS->p_critical()) {
1206 double Ts = pure_saturation.
evaluate(
iT, _p, _Q, iL, iV);
1207 double TL = single_phase_logpT.T[cached_single_phase_i][cached_single_phase_j];
1208 double TR = single_phase_logpT.T[cached_single_phase_i + 1][cached_single_phase_j];
1209 if (TL < Ts && Ts < TR) {
1211 if (cached_single_phase_i == 0) {
1212 throw ValueError(
format(
"P, T are near saturation, but cannot move the cell to the left"));
1215 cached_single_phase_i--;
1217 if (cached_single_phase_i > single_phase_logpT.
Nx - 2) {
1218 throw ValueError(
format(
"P,T are near saturation, but cannot move the cell to the right"));
1221 cached_single_phase_i++;
1229 const auto& bumped_cell = dataset->coeffs_pT[cached_single_phase_i][cached_single_phase_j];
1230 if (!bumped_cell.valid()) {
1231 if (
auto alt = bumped_cell.get_alternate()) {
1232 auto [ai, aj] = *alt;
1233 cached_single_phase_i = ai;
1234 cached_single_phase_j = aj;
1237 format(
"P,T near saturation: bumped cell (%zu, %zu) has no bicubic coefficients and no good neighbour "
1238 "(p=%g Pa, T=%g K, Tsat=%g K)",
1239 cached_single_phase_i, cached_single_phase_j, _p,
static_cast<double>(_T), Ts));
1246 recalculate_singlephase_phase();
1256 switch (input_pair) {
1279 using_single_phase_table =
true;
1280 std::size_t iL = std::numeric_limits<std::size_t>::max(), iV = std::numeric_limits<std::size_t>::max();
1282 std::size_t iclosest = 0;
1284 bool is_two_phase =
false;
1292 std::vector<std::pair<std::size_t, std::size_t>> intersect =
1294 if (intersect.size() < 2) {
1297 iV = intersect[0].first;
1298 iL = intersect[1].first;
1303 is_two_phase = pure_saturation.
is_inside(
iP, _p, otherkey, otherval, iL, iV, zL, zV);
1308 using_single_phase_table =
false;
1310 _Q = (1 / otherval - 1 / zL) / (1 / zV - 1 / zL);
1312 _Q = (otherval - zL) / (zV - zL);
1317 otherval,
static_cast<double>(_p)));
1318 }
else if (!is_mixture) {
1319 cached_saturation_iL = iL;
1320 cached_saturation_iV = iV;
1324 selected_table = SELECTED_PH_TABLE;
1326 find_nearest_neighbor(single_phase_logph, dataset->coeffs_ph,
iP, _p, otherkey, otherval, cached_single_phase_i,
1327 cached_single_phase_j);
1329 invert_single_phase_x(single_phase_logph, dataset->coeffs_ph, otherkey, otherval, _p, cached_single_phase_i, cached_single_phase_j);
1331 recalculate_singlephase_phase();
1339 switch (input_pair) {
1356 using_single_phase_table =
true;
1357 std::size_t iL = std::numeric_limits<std::size_t>::max(), iV = std::numeric_limits<std::size_t>::max();
1359 std::size_t iclosest = 0;
1361 bool is_two_phase =
false;
1369 std::vector<std::pair<std::size_t, std::size_t>> intersect =
1371 if (intersect.size() < 2) {
1374 iV = intersect[0].first;
1375 iL = intersect[1].first;
1380 is_two_phase = pure_saturation.
is_inside(
iT, _T, otherkey, otherval, iL, iV, zL, zV);
1384 using_single_phase_table =
false;
1386 _Q = (1 / otherval - 1 / zL) / (1 / zV - 1 / zL);
1388 _Q = (otherval - zL) / (zV - zL);
1392 otherval,
static_cast<double>(_T)));
1393 }
else if (!is_mixture) {
1394 cached_saturation_iL = iL;
1395 cached_saturation_iV = iV;
1396 _p = pure_saturation.
evaluate(
iP, _T, _Q, iL, iV);
1400 if (intersect.size() < 2) {
1403 iV = intersect[0].first;
1404 iL = intersect[1].first;
1407 _p = _Q * pV + (1 - _Q) * pL;
1410 selected_table = SELECTED_PT_TABLE;
1412 find_nearest_neighbor(single_phase_logpT, dataset->coeffs_pT,
iT, _T, otherkey, otherval, cached_single_phase_i,
1413 cached_single_phase_j);
1415 invert_single_phase_y(single_phase_logpT, dataset->coeffs_pT, otherkey, otherval, _T, cached_single_phase_i, cached_single_phase_j);
1417 recalculate_singlephase_phase();
1422 std::size_t iL = 0, iV = 0;
1425 using_single_phase_table =
false;
1427 throw ValueError(
format(
"vapor quality [%g] is not in (0,1)",
static_cast<double>(val2)));
1432 if (intersect.size() < 2) {
1435 iV = intersect[0].first;
1436 iL = intersect[1].first;
1440 bool it_is_inside = pure_saturation.
is_inside(
iP, _p,
iQ, _Q, iL, iV, TL, TV);
1441 if (!it_is_inside) {
1442 throw ValueError(
"Not possible to determine whether pressure is inside or not");
1445 _T = _Q * TV + (1 - _Q) * TL;
1446 cached_saturation_iL = iL;
1447 cached_saturation_iV = iV;
1453 std::size_t iL = 0, iV = 0;
1457 using_single_phase_table =
false;
1459 throw ValueError(
format(
"vapor quality [%g] is not in (0,1)",
static_cast<double>(val1)));
1464 if (intersect.size() < 2) {
1467 iV = intersect[0].first;
1468 iL = intersect[1].first;
1472 (void)pure_saturation.
is_inside(
iT, _T,
iQ, _Q, iL, iV, pL, pV);
1474 _p = _Q * pV + (1 - _Q) * pL;
1475 cached_saturation_iL = iL;
1476 cached_saturation_iV = iV;
1482 throw ValueError(
"Sorry, but this set of inputs is not supported for Tabular backend");
1488 write_table(single_phase_logph, path_to_tables,
"single_phase_logph");
1489 write_table(single_phase_logpT, path_to_tables,
"single_phase_logpT");
1490 write_table(pure_saturation, path_to_tables,
"pure_saturation");
1491 write_table(phase_envelope, path_to_tables,
"phase_envelope");
1495 single_phase_logph.AS = AS;
1496 single_phase_logpT.AS = AS;
1497 pure_saturation.AS = AS;
1498 single_phase_logph.set_limits();
1499 single_phase_logpT.set_limits();
1500 load_table(single_phase_logph, path_to_tables,
"single_phase_logph.bin.z");
1501 load_table(single_phase_logpT, path_to_tables,
"single_phase_logpT.bin.z");
1502 load_table(pure_saturation, path_to_tables,
"pure_saturation.bin.z");
1503 load_table(phase_envelope, path_to_tables,
"phase_envelope.bin.z");
1504 tables_loaded =
true;
1506 std::cout <<
"Tables loaded" <<
'\n';
1512 if (AS->get_mole_fractions().size() == 1) {
1513 pure_saturation.build(AS);
1516 AS->build_phase_envelope(
"");
1520 phase_envelope.copy_from_nonpackable(PED);
1522 pure_saturation.
resize(pure_saturation.N);
1524 single_phase_logph.build(AS);
1525 single_phase_logpT.build(AS);
1526 tables_loaded =
true;
1531 const std::string path = path_to_tables(AS);
1535 auto it = data.find(path);
1536 if (it != data.end()) {
1542 const bool grid_matches =
1549 std::cout <<
format(
"TABULAR_NX/NY changed (cached %zux%zu, config %dx%d); evicting cached dataset for %s\n",
1556 data.insert(std::pair<std::string, TabularDataSet>(path, set));
1558 bool loaded =
false;
1564 }
catch (std::exception&) {
1567 return {&(dataset), loaded};
1571 if (!coeffs.empty()) {
1575 const int param_count = 6;
1577 std::vector<std::vector<double>>*f =
nullptr, *fx =
nullptr, *fy =
nullptr, *fxy =
nullptr;
1579 clock_t t1 = clock();
1582 coeffs.resize(table.
Nx - 1, std::vector<CellCoeffs>(table.
Ny - 1));
1584 int valid_cell_count = 0;
1585 for (
auto param : param_list) {
1586 if (param == table.
xkey || param == table.
ykey) {
1595 fxy = &(table.d2Tdxdy);
1601 fxy = &(table.d2pdxdy);
1604 f = &(table.rhomolar);
1605 fx = &(table.drhomolardx);
1606 fy = &(table.drhomolardy);
1607 fxy = &(table.d2rhomolardxdy);
1610 f = &(table.smolar);
1611 fx = &(table.dsmolardx);
1612 fy = &(table.dsmolardy);
1613 fxy = &(table.d2smolardxdy);
1616 f = &(table.hmolar);
1617 fx = &(table.dhmolardx);
1618 fy = &(table.dhmolardy);
1619 fxy = &(table.d2hmolardxdy);
1622 f = &(table.umolar);
1623 fx = &(table.dumolardx);
1624 fy = &(table.dumolardy);
1625 fxy = &(table.d2umolardxdy);
1628 throw ValueError(
"Invalid variable type to build_coeffs");
1630 for (std::size_t i = 0; i < table.
Nx - 1; ++i)
1632 for (std::size_t j = 0; j < table.
Ny - 1; ++j)
1637 Eigen::Matrix<double, 16, 1> F;
1640 F(1) = (*f)[i + 1][j];
1641 F(2) = (*f)[i][j + 1];
1642 F(3) = (*f)[i + 1][j + 1];
1645 coeffs[i][j].dx_dxhat = table.
xvec[i + 1] - table.
xvec[i];
1646 double dx_dxhat = coeffs[i][j].dx_dxhat;
1647 F(4) = (*fx)[i][j] * dx_dxhat;
1648 F(5) = (*fx)[i + 1][j] * dx_dxhat;
1649 F(6) = (*fx)[i][j + 1] * dx_dxhat;
1650 F(7) = (*fx)[i + 1][j + 1] * dx_dxhat;
1653 coeffs[i][j].dy_dyhat = table.
yvec[j + 1] - table.
yvec[j];
1654 double dy_dyhat = coeffs[i][j].dy_dyhat;
1655 F(8) = (*fy)[i][j] * dy_dyhat;
1656 F(9) = (*fy)[i + 1][j] * dy_dyhat;
1657 F(10) = (*fy)[i][j + 1] * dy_dyhat;
1658 F(11) = (*fy)[i + 1][j + 1] * dy_dyhat;
1660 F(12) = (*fxy)[i][j] * dy_dyhat * dx_dxhat;
1661 F(13) = (*fxy)[i + 1][j] * dy_dyhat * dx_dxhat;
1662 F(14) = (*fxy)[i][j + 1] * dy_dyhat * dx_dxhat;
1663 F(15) = (*fxy)[i + 1][j + 1] * dy_dyhat * dx_dxhat;
1665 Eigen::MatrixXd alpha = Ainv.transpose() * F;
1667 coeffs[i][j].set(param, valpha);
1668 coeffs[i][j].set_valid();
1671 coeffs[i][j].set_invalid();
1675 double elapsed = (clock() - t1) / ((
double)CLOCKS_PER_SEC);
1677 std::cout <<
format(
"Calculated bicubic coefficients for %d good cells in %g sec.\n", valid_cell_count, elapsed);
1679 std::size_t remap_count = 0;
1681 for (std::size_t i = 0; i < table.
Nx - 1; ++i)
1683 for (std::size_t j = 0; j < table.
Ny - 1; ++j)
1686 if (!coeffs[i][j].valid()) {
1688 int xoffsets[] = {-1, 1, 0, 0, -1, 1, 1, -1};
1689 int yoffsets[] = {0, 0, 1, -1, -1, -1, 1, 1};
1691 std::size_t N =
sizeof(xoffsets) /
sizeof(xoffsets[0]);
1692 for (std::size_t k = 0; k < N; ++k) {
1693 std::size_t iplus = i + xoffsets[k];
1694 std::size_t jplus = j + yoffsets[k];
1695 if (0 < iplus && iplus < table.
Nx - 1 && 0 < jplus && jplus < table.
Ny - 1 && coeffs[iplus][jplus].valid()) {
1696 coeffs[i][j].set_alternate(iplus, jplus);
1699 std::cout <<
format(
"Mapping %d,%d to %d,%d\n", i, j, iplus, jplus);
1708 std::cout <<
format(
"Remapped %d cells\n", remap_count);
1713# if defined(ENABLE_CATCH)
1714# include <catch2/catch_all.hpp>
1717static shared_ptr<CoolProp::AbstractState> ASHEOS, ASTTSE, ASBICUBIC;
1723 TabularFixture() =
default;
1725 if (ASHEOS.get() ==
nullptr) {
1728 if (ASTTSE.get() ==
nullptr) {
1731 if (ASBICUBIC.get() ==
nullptr) {
1736TEST_CASE_METHOD(TabularFixture,
"Tests for tabular backends with water",
"[Tabular]") {
1737 SECTION(
"first_saturation_deriv invalid quality") {
1745 SECTION(
"first_saturation_deriv dp/dT") {
1754 CAPTURE(actual_TTSE);
1755 CAPTURE(actual_BICUBIC);
1756 CHECK(std::abs((expected - actual_TTSE) / expected) < 1e-6);
1757 CHECK(std::abs((expected - actual_BICUBIC) / expected) < 1e-6);
1759 SECTION(
"first_saturation_deriv dDmolar/dP") {
1768 CAPTURE(actual_TTSE);
1769 CAPTURE(actual_BICUBIC);
1770 CHECK(std::abs((expected - actual_TTSE) / expected) < 1e-6);
1771 CHECK(std::abs((expected - actual_BICUBIC) / expected) < 1e-6);
1773 SECTION(
"first_saturation_deriv dDmass/dP") {
1782 CAPTURE(actual_TTSE);
1783 CAPTURE(actual_BICUBIC);
1784 CHECK(std::abs((expected - actual_TTSE) / expected) < 1e-6);
1785 CHECK(std::abs((expected - actual_BICUBIC) / expected) < 1e-6);
1787 SECTION(
"first_saturation_deriv dHmass/dP") {
1796 CAPTURE(actual_TTSE);
1797 CAPTURE(actual_BICUBIC);
1798 CHECK(std::abs((expected - actual_TTSE) / expected) < 1e-6);
1799 CHECK(std::abs((expected - actual_BICUBIC) / expected) < 1e-6);
1801 SECTION(
"first_saturation_deriv dHmass/dP w/ QT as inputs") {
1810 CAPTURE(actual_TTSE);
1811 CAPTURE(actual_BICUBIC);
1812 CHECK(std::abs((expected - actual_TTSE) / expected) < 1e-6);
1813 CHECK(std::abs((expected - actual_BICUBIC) / expected) < 1e-6);
1815 SECTION(
"first_two_phase_deriv dDmolar/dP|Hmolar") {
1824 CAPTURE(actual_TTSE);
1825 CAPTURE(actual_BICUBIC);
1826 CHECK(std::abs((expected - actual_TTSE) / expected) < 1e-6);
1827 CHECK(std::abs((expected - actual_BICUBIC) / expected) < 1e-6);
1829 SECTION(
"first_two_phase_deriv dDmass/dP|Hmass") {
1838 CAPTURE(actual_TTSE);
1839 CAPTURE(actual_BICUBIC);
1840 CHECK(std::abs((expected - actual_TTSE) / expected) < 1e-6);
1841 CHECK(std::abs((expected - actual_BICUBIC) / expected) < 1e-6);
1843 SECTION(
"first_two_phase_deriv dDmass/dHmass|P") {
1852 CAPTURE(actual_TTSE);
1853 CAPTURE(actual_BICUBIC);
1854 CHECK(std::abs((expected - actual_TTSE) / expected) < 1e-6);
1855 CHECK(std::abs((expected - actual_BICUBIC) / expected) < 1e-6);
1857 SECTION(
"first_partial_deriv dHmass/dT|P") {
1860 double expected = ASHEOS->cpmass();
1867 CAPTURE(dhdT_BICUBIC);
1868 CHECK(std::abs((expected - dhdT_TTSE) / expected) < 1e-4);
1869 CHECK(std::abs((expected - dhdT_BICUBIC) / expected) < 1e-4);
1871 SECTION(
"first_partial_deriv dHmolar/dT|P") {
1874 double expected = ASHEOS->cpmolar();
1881 CAPTURE(dhdT_BICUBIC);
1882 CHECK(std::abs((expected - dhdT_TTSE) / expected) < 1e-4);
1883 CHECK(std::abs((expected - dhdT_BICUBIC) / expected) < 1e-4);
1885 SECTION(
"check isentropic process") {
1891 double s0 = ASHEOS->smolar();
1893 double expected = ASHEOS->T();
1895 double actual_TTSE = ASTTSE->T();
1897 double actual_BICUBIC = ASBICUBIC->T();
1899 CAPTURE(actual_TTSE);
1900 CAPTURE(actual_BICUBIC);
1901 CHECK(std::abs((expected - actual_TTSE) / expected) < 1e-2);
1902 CHECK(std::abs((expected - actual_BICUBIC) / expected) < 1e-2);
1904 SECTION(
"check D=1 mol/m3, T=500 K inputs") {
1909 double expected = ASHEOS->p();
1911 double actual_TTSE = ASTTSE->p();
1913 double actual_BICUBIC = ASBICUBIC->p();
1915 CAPTURE(actual_TTSE);
1916 CAPTURE(actual_BICUBIC);
1917 CHECK(std::abs((expected - actual_TTSE) / expected) < 1e-3);
1918 CHECK(std::abs((expected - actual_BICUBIC) / expected) < 1e-3);
1921 SECTION(
"fast_evaluate single point matches keyed_output (PT, BICUBIC)") {
1923 const double T = 400, p = 1e6;
1926 const double ref_D = ASHEOS->rhomolar();
1927 const double ref_H = ASHEOS->hmolar();
1928 const double ref_S = ASHEOS->smolar();
1930 const double val1[1] = {p};
1931 const double val2[1] = {
T};
1933 double buf[3] = {0, 0, 0};
1934 int status[1] = {-1};
1938 CHECK(std::abs((ref_D - buf[0]) / ref_D) < 1e-3);
1939 CHECK(std::abs((ref_H - buf[1]) / ref_H) < 1e-3);
1940 CHECK(std::abs((ref_S - buf[2]) / ref_S) < 1e-3);
1943 SECTION(
"fast_evaluate batch — Hmolar/p inputs over a range (BICUBIC)") {
1945 constexpr std::size_t N = 32;
1946 const double p = 5e5;
1947 std::vector<double> hs(N), ps(N, p);
1950 const double h_satV = ASHEOS->hmolar();
1951 for (std::size_t k = 0; k < N; ++k) {
1952 hs[k] = h_satV + 100.0 + k * 50.0;
1955 std::vector<double> buf(2 * N, 0);
1956 std::vector<int> status(N, -1);
1957 ASBICUBIC->fast_evaluate(
CoolProp::HmolarP_INPUTS, hs.data(), ps.data(), N, outs, 2, buf.data(), buf.size(), status.data(), status.size());
1958 for (std::size_t k = 0; k < N; ++k) {
1964 CHECK(std::abs((ASBICUBIC->T() - buf[2 * k + 0]) / ASBICUBIC->T()) < 1e-9);
1965 CHECK(std::abs((ASBICUBIC->rhomolar() - buf[2 * k + 1]) / ASBICUBIC->rhomolar()) < 1e-9);
1969 SECTION(
"fast_evaluate validates buffer sizes") {
1971 const double v1[1] = {1e6};
1972 const double v2[1] = {400};
1974 double buf[2] = {0, 0};
1975 int status[1] = {0};
1977 CHECK_THROWS(ASBICUBIC->fast_evaluate(
CoolProp::PT_INPUTS, v1, v2, 1, outs, 2, buf, 1, status, 1));
1979 CHECK_THROWS(ASBICUBIC->fast_evaluate(
CoolProp::PT_INPUTS, v1, v2, 1, outs, 2, buf, 2, status, 0));
1981 CHECK_THROWS(ASBICUBIC->fast_evaluate(
CoolProp::QT_INPUTS, v1, v2, 1, outs, 2, buf, 2, status, 1));
1984 SECTION(
"fast_evaluate out-of-range point reports NaN + flag, others ok") {
1986 const double v1[2] = {1e6, 1e20};
1987 const double v2[2] = {400, 400};
1989 double buf[2] = {-1, -1};
1990 int status[2] = {-1, -1};
1994 CHECK(std::isnan(buf[1]));
1999TEST_CASE(
"fast_evaluate works with IF97 backend",
"[fast_evaluate][IF97]") {
2003 SECTION(
"PT batch matches the cached path") {
2004 constexpr std::size_t N = 16;
2005 std::vector<double> ps(N), Ts(N);
2006 for (std::size_t k = 0; k < N; ++k) {
2007 ps[k] = 1e5 + k * 1e4;
2008 Ts[k] = 500 + k * 5.0;
2011 std::vector<double> buf(3 * N, 0);
2012 std::vector<int> status(N, -1);
2013 AS->fast_evaluate(
PT_INPUTS, ps.data(), Ts.data(), N, outs, 3, buf.data(), buf.size(), status.data(), status.size());
2014 for (std::size_t k = 0; k < N; ++k) {
2018 CHECK(std::abs((AS->rhomass() - buf[3 * k + 0]) / AS->rhomass()) < 1e-12);
2019 CHECK(std::abs((AS->hmass() - buf[3 * k + 1]) / AS->hmass()) < 1e-12);
2020 CHECK(std::abs((AS->smass() - buf[3 * k + 2]) / AS->smass()) < 1e-12);
2025TEST_CASE(
"fast_evaluate is not implemented for HEOS backend",
"[fast_evaluate][HEOS]") {
2028 const double v1[1] = {1e6};
2029 const double v2[1] = {400};
2031 double buf[1] = {0};
2032 int status[1] = {0};
2033 CHECK_THROWS(AS->fast_evaluate(
PT_INPUTS, v1, v2, 1, outs, 1, buf, 1, status, 1));