1#ifndef COOLPROP_REGION_SUPERANCILLARY_TEMPERATURE_BOUNDARY_CURVE_H
2#define COOLPROP_REGION_SUPERANCILLARY_TEMPERATURE_BOUNDARY_CURVE_H
39 double output_scale,
double b_min,
double b_max)
40 : sa_(std::move(sa)), T_min_(T_min), T_max_(T_max), prop_key_(prop_key), Q_(Q), output_scale_(output_scale), b_min_(b_min), b_max_(b_max) {
46 throw std::invalid_argument(
"SuperancillaryTemperatureBoundaryCurve: null SuperAncillary handle");
48 if (!(T_min_ > 0.0) || !(T_max_ > T_min_)) {
49 throw std::invalid_argument(
"SuperancillaryTemperatureBoundaryCurve: need 0 < T_min < T_max");
55 if (Q_ != 0 && Q_ != 1) {
56 throw std::invalid_argument(
"SuperancillaryTemperatureBoundaryCurve: Q must be 0 (liquid) or 1 (vapor)");
66 throw std::invalid_argument(
"SuperancillaryTemperatureBoundaryCurve: unsupported prop_key (expect one of D,P,H,S,U)");
78 static std::unique_ptr<SuperancillaryTemperatureBoundaryCurve>
build(std::shared_ptr<SuperAncillary_t> sa,
double T_min,
double T_max,
79 char prop_key,
short Q,
double output_scale);
81 [[nodiscard]]
double eval(
double T)
const noexcept override {
83 return sa_->eval_sat(
T, prop_key_, Q_) * output_scale_;
89 [[nodiscard]]
double eval_da(
double T)
const noexcept override {
96 const double h = std::max(1e-7 * std::abs(
T), 1.0);
97 const double T_hi = std::min(
T + h, T_max_);
98 const double T_lo = std::max(
T - h, T_min_);
102 const double y_plus =
eval(T_hi);
103 const double y_minus =
eval(T_lo);
104 if (!std::isfinite(y_plus) || !std::isfinite(y_minus)) {
107 return (y_plus - y_minus) / (T_hi - T_lo);
113 [[nodiscard]]
double eval_fast(
double T)
const noexcept override {
114 const double idx_f = (
T - T_min_) * inv_T_step_;
115 if (!(idx_f > 0.0)) {
116 return surrogate_table_.front();
118 const auto last =
static_cast<double>(kSurrogatePoints - 1);
120 return surrogate_table_.back();
122 const auto i =
static_cast<std::size_t
>(idx_f);
123 const double frac = idx_f -
static_cast<double>(i);
124 const double y0 = surrogate_table_[i];
125 const double y1 = surrogate_table_[i + 1];
126 return y0 + frac * (y1 - y0);
129 [[nodiscard]] std::pair<double, double>
bounds() const noexcept
override {
130 return {b_min_, b_max_};
132 [[nodiscard]] std::pair<double, double>
a_range() const noexcept
override {
133 return {T_min_, T_max_};
150 return State{T_min_, T_max_, prop_key_, Q_, output_scale_, b_min_, b_max_};
152 static std::unique_ptr<SuperancillaryTemperatureBoundaryCurve>
from_state(
State s, std::shared_ptr<SuperAncillary_t> sa) {
154 throw std::invalid_argument(
"SuperancillaryTemperatureBoundaryCurve::from_state: null SuperAncillary handle");
161 static constexpr std::size_t kSurrogatePoints = 1024;
165 void build_surrogate_() {
166 surrogate_table_.resize(kSurrogatePoints);
167 const double T_step = (T_max_ - T_min_) /
static_cast<double>(kSurrogatePoints - 1);
168 inv_T_step_ = 1.0 / T_step;
169 bool any_finite =
false;
170 for (std::size_t k = 0; k < kSurrogatePoints; ++k) {
171 const double T = T_min_ +
static_cast<double>(k) * T_step;
172 surrogate_table_[k] =
eval(
T);
173 any_finite = any_finite || std::isfinite(surrogate_table_[k]);
181 throw std::runtime_error(
"SuperancillaryTemperatureBoundaryCurve: no finite samples in surrogate build range");
191 for (std::size_t k = 0; k < kSurrogatePoints; ++k) {
192 if (std::isfinite(surrogate_table_[k]))
continue;
193 double fill = std::nan(
"");
194 for (std::size_t j = k; j-- > 0;) {
195 if (std::isfinite(surrogate_table_[j])) {
196 fill = surrogate_table_[j];
200 if (!std::isfinite(fill)) {
201 for (std::size_t j = k + 1; j < kSurrogatePoints; ++j) {
202 if (std::isfinite(surrogate_table_[j])) {
203 fill = surrogate_table_[j];
208 surrogate_table_[k] = fill;
212 std::shared_ptr<SuperAncillary_t> sa_;
217 double output_scale_;
220 std::vector<double> surrogate_table_;
221 double inv_T_step_ = 0.0;