57std::vector<double> chebyshev_lobatto_T_knots(
double T_lo,
double T_hi, std::size_t n) {
60 std::vector<double>
T(n);
61 const double mid = 0.5 * (T_lo + T_hi);
62 const double half = 0.5 * (T_hi - T_lo);
63 const auto denom =
static_cast<double>(n - 1);
64 for (std::size_t k = 0; k < n; ++k) {
65 const double theta =
M_PI *
static_cast<double>(k) / denom;
66 T[k] = mid - half * std::cos(theta);
77constexpr std::size_t kMinKnots = 4;
78constexpr double kCriticalAvoidance = 0.9999;
84 std::unique_ptr<region::CubicSplineCurve>
P_of_T;
85 std::array<std::unique_ptr<region::CubicSplineCurve>, 2>
D_of_T;
86 std::array<std::unique_ptr<region::CubicSplineCurve>, 2>
H_of_T;
87 std::array<std::unique_ptr<region::CubicSplineCurve>, 2>
S_of_T;
88 std::array<std::unique_ptr<region::CubicSplineCurve>, 2>
U_of_T;
89 std::unique_ptr<region::CubicSplineCurve>
T_of_logp;
97SaturationSurrogate::SaturationSurrogate() : impl_(std::make_unique<
Impl>()) {}
104 double T_triple = 0.0;
109 }
catch (
const std::exception&) {
116 const double T_lo = T_triple * (1.0 + 1.0e-6);
117 const double T_hi = T_crit * kCriticalAvoidance;
118 if (!(T_lo > 0.0) || !(T_hi > T_lo)) {
122 const auto T_knots = chebyshev_lobatto_T_knots(T_lo, T_hi,
n_knots);
124 std::vector<double> p_col(
n_knots);
125 std::array<std::vector<double>, 2> D_col = {std::vector<double>(
n_knots), std::vector<double>(
n_knots)};
126 std::array<std::vector<double>, 2> H_col = {std::vector<double>(
n_knots), std::vector<double>(
n_knots)};
127 std::array<std::vector<double>, 2> S_col = {std::vector<double>(
n_knots), std::vector<double>(
n_knots)};
128 std::array<std::vector<double>, 2> U_col = {std::vector<double>(
n_knots), std::vector<double>(
n_knots)};
130 for (std::size_t k = 0; k <
n_knots; ++k) {
131 const double T = T_knots[k];
133 for (
int side : {0, 1}) {
136 }
catch (
const std::exception&) {
143 H_col[side][k] = src.
hmolar();
144 S_col[side][k] = src.
smolar();
145 U_col[side][k] = src.
umolar();
150 if (!std::isfinite(D_col[side][k]) || !std::isfinite(H_col[side][k]) || !std::isfinite(S_col[side][k])
151 || !std::isfinite(U_col[side][k])) {
159 if (!std::isfinite(p_k) || p_k <= 0.0) {
162 if (k > 0 && !(p_k > p_col[k - 1])) {
168 auto& impl = *surrogate->impl_;
171 for (
int side : {0, 1}) {
177 std::vector<double> logp_col(
n_knots);
178 for (std::size_t k = 0; k <
n_knots; ++k) {
179 logp_col[k] = std::log(p_col[k]);
185 }
catch (
const std::exception&) {
190 impl.p_lo = p_col.front();
191 impl.p_hi = p_col.back();
198 throw std::out_of_range(
"SaturationSurrogate::get_T_from_p: p must be positive");
200 if (p < impl_->p_lo || p > impl_->p_hi) {
201 throw std::out_of_range(
"SaturationSurrogate::get_T_from_p: p outside surrogate range");
203 return impl_->T_of_logp->eval(std::log(p));
207 if (side != 0 && side != 1) {
208 throw std::invalid_argument(
"SaturationSurrogate::eval_sat: side must be 0 (liquid) or 1 (vapor)");
212 if (!std::isfinite(
T)) {
213 throw std::out_of_range(
"SaturationSurrogate::eval_sat: T must be finite");
215 if (T < impl_->T_lo ||
T > impl_->T_hi) {
216 throw std::out_of_range(
"SaturationSurrogate::eval_sat: T outside surrogate range");
220 return impl_->P_of_T->eval(
T);
222 return impl_->D_of_T[side]->eval(
T);
224 return impl_->H_of_T[side]->eval(
T);
226 return impl_->S_of_T[side]->eval(
T);
228 return impl_->U_of_T[side]->eval(
T);
230 throw std::invalid_argument(
"SaturationSurrogate::eval_sat: 'what' must be one of {P, D, H, S, U}");
241 return impl_->n_knots;