13 : fluid_name_(std::move(fluid_name)), input_pair_(input_pair), properties_(std::move(properties)) {
14 if (properties_.empty()) {
15 throw std::invalid_argument(
"SVDSurface: properties list must be non-empty");
20 auto sorted = properties_;
21 std::sort(sorted.begin(), sorted.end());
22 if (std::adjacent_find(sorted.begin(), sorted.end()) != sorted.end()) {
23 throw std::invalid_argument(
"SVDSurface: duplicate property in properties list");
29 throw std::logic_error(
"SVDSurface: add_region called after seal()");
31 const std::size_t idx = atlas_.
add(std::move(region));
33 decomps_.emplace_back();
34 decomps_.back().resize(properties_.size());
35 evaluators_.emplace_back();
36 evaluators_.back().resize(properties_.size());
42 throw std::logic_error(
"SVDSurface: add_region_property_svd called after seal()");
44 if (region_idx >= decomps_.size()) {
45 throw std::out_of_range(
"SVDSurface: region_idx out of range");
47 const std::size_t pidx = property_index(prop);
48 if (decomps_[region_idx][pidx]) {
49 throw std::logic_error(
"SVDSurface: decomposition for this (region, property) already registered");
51 decomps_[region_idx][pidx] = std::make_unique<svd::SVDDecomposition>(std::move(decomp));
59 for (std::size_t r = 0; r < decomps_.size(); ++r) {
60 for (std::size_t p = 0; p < properties_.size(); ++p) {
61 if (!decomps_[r][p]) {
62 throw std::logic_error(
"SVDSurface::seal: region " + std::to_string(r) +
" is missing a decomposition for property index "
74 for (std::size_t r = 0; r < decomps_.size(); ++r) {
75 const auto& x_ref = decomps_[r][0]->x_grid;
76 const auto& y_ref = decomps_[r][0]->y_grid;
77 for (std::size_t p = 1; p < properties_.size(); ++p) {
78 if (decomps_[r][p]->x_grid != x_ref || decomps_[r][p]->y_grid != y_ref) {
79 throw std::logic_error(
"SVDSurface::seal: region " + std::to_string(r) +
" property index " + std::to_string(p)
80 +
" has (x_grid, y_grid) that differ from the region's first property — "
81 "eval_with_region_multi requires identical grids across the region");
88 for (std::size_t r = 0; r < decomps_.size(); ++r) {
89 for (std::size_t p = 0; p < properties_.size(); ++p) {
90 evaluators_[r][p] = std::make_unique<svd::SVDEvaluator>(*decomps_[r][p]);
101 return std::find(properties_.begin(), properties_.end(), prop) != properties_.end();
105 const auto it = std::find(properties_.begin(), properties_.end(), prop);
106 if (it == properties_.end()) {
107 throw std::invalid_argument(
"SVDSurface: property is not tabulated on this surface");
109 return static_cast<std::size_t
>(std::distance(properties_.begin(), it));
113 if (region_idx >= decomps_.size()) {
114 throw std::out_of_range(
"SVDSurface::decomposition: region_idx out of range");
116 const std::size_t pidx = property_index(prop);
117 if (!decomps_[region_idx][pidx]) {
118 throw std::logic_error(
"SVDSurface::decomposition: decomposition is null (surface not sealed?)");
120 return *decomps_[region_idx][pidx];
124 const int region_idx = atlas_.find_region(a, b);
125 if (region_idx < 0) {
126 return ResolvedPoint{-1, std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::quiet_NaN()};
128 const auto [xi, eta] = atlas_.region(
static_cast<std::size_t
>(region_idx)).to_normalized(a, b);
136 throw std::logic_error(
"SVDSurface::eval called before seal()");
138 const auto resolved =
resolve(a, b);
139 if (resolved.region_idx < 0) {
140 return std::numeric_limits<double>::quiet_NaN();
142 return eval_with_region(prop, resolved.region_idx, resolved.svd_x, resolved.svd_y);
147 throw std::logic_error(
"SVDSurface::eval_with_region called before seal()");
149 if (region_idx < 0 ||
static_cast<std::size_t
>(region_idx) >= evaluators_.size()) {
150 throw std::out_of_range(
"SVDSurface::eval_with_region: region_idx out of range");
152 const std::size_t pidx = property_index(prop);
153 return evaluators_[
static_cast<std::size_t
>(region_idx)][pidx]->
eval(svd_x, svd_y);
159 throw std::logic_error(
"SVDSurface::eval_with_region_multi called before seal()");
161 if (region_idx < 0 ||
static_cast<std::size_t
>(region_idx) >= evaluators_.size()) {
162 throw std::out_of_range(
"SVDSurface::eval_with_region_multi: region_idx out of range");
167 const auto& region_evals = evaluators_[
static_cast<std::size_t
>(region_idx)];
175 const std::size_t first_pidx = property_index(props[0]);
177 out[0] = region_evals[first_pidx]->eval_with_context(ctx);
178 for (std::size_t k = 1; k < n; ++k) {
179 const std::size_t pidx = property_index(props[k]);
180 out[k] = region_evals[pidx]->eval_with_context(ctx);