Warning, /epic-analysis/src/interp/Interpolate.ipp is written in an unsupported language. File is not indexed.
0001 // SPDX-License-Identifier: LGPL-3.0-or-later
0002 // Copyright (C) 2023 Duane Byer
0003
0004 #ifndef Interpolate_IPP_
0005 #define Interpolate_IPP_
0006
0007 #include "Interpolate.h"
0008
0009 #include <cmath>
0010 #include <cstddef>
0011 #include <limits>
0012 #include <stdexcept>
0013 #include <string>
0014 #include <type_traits>
0015
0016 // Version of `modf` with correct sign behaviour.
0017 template<typename T>
0018 T modf(T x, T* iptr) {
0019 if (!(x < 0.)) {
0020 return std::modf(x, iptr);
0021 } else {
0022 T frac = std::modf(x, iptr);
0023 frac += 1;
0024 *iptr -= 1;
0025 return frac;
0026 }
0027 }
0028
0029 template<typename T, std::size_t N>
0030 GridView<T, N>::GridView(T const* data, Coords<T, N> coords) :
0031 _data(data),
0032 _coords(coords) {
0033 _count_total = 1;
0034 for (std::size_t idx = 0; idx < N; ++idx) {
0035 _count_total *= _coords[idx].size();
0036 if (_coords[idx].size() <= 1) {
0037 throw SingularDimensionError(idx);
0038 }
0039 }
0040 }
0041
0042 template<typename T, std::size_t N>
0043 GridView<T, N - 1> GridView<T, N>::operator[](std::size_t idx) const {
0044 std::size_t width = _count_total / count(0);
0045 std::array<std::vector<T>, N - 1> coords_red;
0046 std::copy(_coords.begin() + 1, _coords.end(), coords_red.begin());
0047 return GridView<T, N - 1>(_data + idx * width, coords_red);
0048 }
0049
0050 template<typename T, std::size_t N>
0051 T const& GridView<T, N>::operator[](CellIndex<N> idx) const {
0052 CellIndex<N - 1> idx_red;
0053 std::copy(idx.begin() + 1, idx.end(), idx_red.begin());
0054 return (*this)[idx[0]][idx_red];
0055 }
0056
0057 template<typename T, std::size_t N>
0058 Grid<T, N>::Grid(T const* data, Coords<T, N> coords) :
0059 _data(),
0060 _coords(coords) {
0061 _count_total = 1;
0062 for (std::size_t idx = 0; idx < N; ++idx) {
0063 _count_total *= _coords[idx].size();
0064 if (_coords[idx].size() <= 1) {
0065 throw SingularDimensionError(idx);
0066 }
0067 }
0068 _data = std::vector<T>(data, data + _count_total);
0069 }
0070
0071 template<typename T, std::size_t N>
0072 T GridView<T, N>::space_from_grid(std::size_t dim, std::size_t idx) const {
0073 return _coords[dim][idx];
0074 }
0075 template<typename T, std::size_t N>
0076 std::size_t GridView<T, N>::grid_from_space(std::size_t dim, T x) const {
0077 T frac;
0078 return grid_from_space(dim, x, &frac);
0079 }
0080 template<typename T, std::size_t N>
0081 std::size_t GridView<T, N>::grid_from_space(std::size_t dim, T x, T* frac) const {
0082 std::size_t lower = 0;
0083 std::size_t upper = _coords[dim].size() - 1;
0084 T lower_x = _coords[dim][lower];
0085 T upper_x = _coords[dim][upper];
0086 while (lower + 1 < upper) {
0087 T diff = (x - lower_x) / (upper_x - lower_x);
0088 if (!(diff >= 0.) || !(diff <= 1.)) {
0089 return std::numeric_limits<std::size_t>::max();
0090 }
0091 T idx_float;
0092 modf(diff * (upper - lower - 1), &idx_float);
0093 std::size_t idx = static_cast<std::size_t>(idx_float) + lower;
0094 T lower_x_new = _coords[dim][idx];
0095 T upper_x_new = _coords[dim][idx + 1];
0096 if (x >= lower_x_new && x < upper_x_new) {
0097 lower = idx;
0098 upper = idx + 1;
0099 lower_x = lower_x_new;
0100 upper_x = upper_x_new;
0101 } else if (x >= upper_x_new) {
0102 lower = idx + 1;
0103 lower_x = upper_x_new;
0104 } else if (x < lower_x_new) {
0105 upper = idx;
0106 upper_x = lower_x_new;
0107 }
0108 }
0109 *frac = x - lower_x;
0110 return lower;
0111 }
0112
0113 template<typename T, std::size_t N>
0114 Point<T, N> GridView<T, N>::space_from_grid(CellIndex<N> idx) const {
0115 Point<T, N> result;
0116 for (std::size_t dim = 0; dim < N; ++dim) {
0117 result[dim] = space_from_grid(dim, idx[dim]);
0118 }
0119 return result;
0120 }
0121 template<typename T, std::size_t N>
0122 CellIndex<N> GridView<T, N>::grid_from_space(Point<T, N> x) const {
0123 CellIndex<N> result;
0124 for (std::size_t dim = 0; dim < N; ++dim) {
0125 result[dim] = grid_from_space(dim, x[dim]);
0126 }
0127 return result;
0128 }
0129 template<typename T, std::size_t N>
0130 CellIndex<N> GridView<T, N>::grid_from_space(Point<T, N> x, Point<T, N>* frac) const {
0131 CellIndex<N> result;
0132 for (std::size_t dim = 0; dim < N; ++dim) {
0133 result[dim] = grid_from_space(dim, x[dim], &frac[dim]);
0134 }
0135 return result;
0136 }
0137
0138 template<typename T, std::size_t N>
0139 T LinearView<T, N>::operator()(Point<T, N> x) const {
0140 T frac;
0141 std::size_t idx = _grid.grid_from_space(0, x[0], &frac);
0142 Point<T, N - 1> x_next;
0143 std::copy(x.begin() + 1, x.end(), x_next.begin());
0144 if (idx >= 0 && idx < _grid.count(0) - 1) {
0145 T linear_1 = LinearView<T, N - 1>(_grid.reduce(idx))(x_next);
0146 T linear_2 = LinearView<T, N - 1>(_grid.reduce(idx + 1))(x_next);
0147 T delta = _grid.space_from_grid(0, idx + 1) - _grid.space_from_grid(0, idx);
0148 return linear(linear_1, linear_2, delta, frac);
0149 } else {
0150 return std::numeric_limits<T>::quiet_NaN();
0151 }
0152 }
0153
0154 template<typename T, std::size_t N>
0155 T CubicView<T, N>::operator()(Point<T, N> x) const {
0156 T frac;
0157 std::size_t idx = _grid.grid_from_space(0, x[0], &frac);
0158 Point<T, N - 1> x_next;
0159 std::copy(x.begin() + 1, x.end(), x_next.begin());
0160 if (idx >= 0 && idx < _grid.count(0) - 1) {
0161 T cubic_0 = idx == 0 ? 0. :
0162 CubicView<T, N - 1>(_grid.reduce(idx - 1))(x_next);
0163 T cubic_1 = CubicView<T, N - 1>(_grid.reduce(idx))(x_next);
0164 T cubic_2 = CubicView<T, N - 1>(_grid.reduce(idx + 1))(x_next);
0165 T cubic_3 = idx >= _grid.count(0) - 2 ? 0. :
0166 CubicView<T, N - 1>(_grid.reduce(idx + 2))(x_next);
0167 T delta_0 = idx == 0 ? std::numeric_limits<T>::infinity() :
0168 _grid.space_from_grid(0, idx) - _grid.space_from_grid(0, idx - 1);
0169 T delta_1 = _grid.space_from_grid(0, idx + 1) - _grid.space_from_grid(0, idx);
0170 T delta_2 = idx >= _grid.count(0) - 2 ? std::numeric_limits<T>::infinity() :
0171 _grid.space_from_grid(0, idx + 2) - _grid.space_from_grid(0, idx + 1);
0172 return cubic(cubic_0, cubic_1, cubic_2, cubic_3, delta_0, delta_1, delta_2, frac);
0173 } else {
0174 return std::numeric_limits<T>::quiet_NaN();
0175 }
0176 }
0177
0178 template<typename T, std::size_t N, std::size_t K>
0179 inline std::array<Grid<T, N>, K> read_grids(
0180 std::vector<std::array<T, N + K> > const& raw_data) {
0181 std::vector<Point<T, N> > grid_points(raw_data.size());
0182 std::vector<std::array<T, K> > data(raw_data.size());
0183 for (std::size_t row_idx = 0; row_idx < raw_data.size(); ++row_idx) {
0184 for (std::size_t dim_idx = 0; dim_idx < N; ++dim_idx) {
0185 grid_points[row_idx][dim_idx] = raw_data[row_idx][dim_idx];
0186 }
0187 for (std::size_t col_idx = 0; col_idx < K; ++col_idx) {
0188 data[row_idx][col_idx] = raw_data[row_idx][N + col_idx];
0189 }
0190 }
0191
0192 // If there are not enough grid points to form one unit cell, then give up.
0193 if (grid_points.size() < (1 << N)) {
0194 throw NotEnoughPointsError(grid_points.size(), 1 << N);
0195 }
0196
0197 std::size_t count_total = 1;
0198 std::array<bool, N> filled_cols = { false };
0199 std::array<std::size_t, N> dim_permute_map;
0200 CellIndex<N> count;
0201 std::array<std::vector<T>, N> coords;
0202 for (std::size_t dim_idx = 0; dim_idx < N; ++dim_idx) {
0203 std::size_t sub_count = grid_points.size() / count_total;
0204 if (sub_count * count_total != grid_points.size()) {
0205 throw NotEnoughPointsError(
0206 grid_points.size(),
0207 (sub_count + 1) * count_total);
0208 }
0209 // Find the column that steps by `count_total`.
0210 std::size_t step_dim_idx = 0;
0211 while (true) {
0212 if (step_dim_idx == N) {
0213 throw NotEnoughPointsError(
0214 grid_points.size(),
0215 2 * grid_points.size());
0216 }
0217 if (filled_cols[step_dim_idx]) {
0218 step_dim_idx += 1;
0219 continue;
0220 }
0221 T initial_point = grid_points[0][step_dim_idx];
0222 T final_point = grid_points[count_total][step_dim_idx];
0223 if (initial_point == final_point) {
0224 step_dim_idx += 1;
0225 continue;
0226 }
0227 bool same = true;
0228 for (std::size_t row_idx = 0; row_idx < count_total; ++row_idx) {
0229 if (grid_points[row_idx][step_dim_idx] != initial_point) {
0230 same = false;
0231 break;
0232 }
0233 }
0234 if (!same) {
0235 step_dim_idx += 1;
0236 continue;
0237 }
0238 filled_cols[step_dim_idx] = true;
0239 dim_permute_map[dim_idx] = step_dim_idx;
0240 break;
0241 }
0242
0243 // Use the column to fill a vector of the grid spacings.
0244 std::vector<T> next_coords;
0245 for (std::size_t group_idx = 0; group_idx < sub_count; ++group_idx) {
0246 T next = grid_points[group_idx * count_total][step_dim_idx];
0247 if (next_coords.empty() || next != next_coords[0]) {
0248 next_coords.push_back(next);
0249 } else {
0250 break;
0251 }
0252 }
0253 std::size_t next_count = next_coords.size();
0254 count[step_dim_idx] = next_count;
0255 if (next_count <= 1) {
0256 throw SingularDimensionError(step_dim_idx);
0257 }
0258
0259 // Check that the data in the column is valid.
0260 for (std::size_t row_idx = 0; row_idx < grid_points.size(); ++row_idx) {
0261 T next = grid_points[row_idx][step_dim_idx];
0262 std::size_t group_idx = row_idx / count_total;
0263 if (next != next_coords[group_idx % next_count]) {
0264 throw UnexpectedGridPointError(row_idx);
0265 }
0266 }
0267
0268 // Check that the grid is increasing.
0269 for (std::size_t idx = 1; idx < next_coords.size(); ++idx) {
0270 if (!(next_coords[idx] > next_coords[idx - 1])) {
0271 throw InvalidGridPlanesError();
0272 }
0273 }
0274 coords[step_dim_idx] = next_coords;
0275
0276 // Increase count for next round.
0277 count_total *= next_count;
0278 }
0279
0280 // Use the grid information to reorder the data.
0281 std::array<std::vector<T>, K> data_transposed;
0282 std::array<std::size_t, N> count_totals = { count_total / count[0] };
0283 for (std::size_t dim_idx = 1; dim_idx < N; ++dim_idx) {
0284 count_totals[dim_idx] = count_totals[dim_idx - 1] / count[dim_idx];
0285 }
0286 for (std::size_t col_idx = 0; col_idx < K; ++col_idx) {
0287 data_transposed[col_idx].resize(data.size());
0288 }
0289 for (std::size_t row_idx = 0; row_idx < data.size(); ++row_idx) {
0290 std::size_t new_row_idx = 0;
0291 std::size_t new_count = 1;
0292 for (std::size_t dim_idx = 0; dim_idx < N; ++dim_idx) {
0293 std::size_t new_dim_idx = dim_permute_map[dim_idx];
0294 std::size_t rel_idx = (row_idx / new_count) % count[new_dim_idx];
0295 std::size_t old_count = count_totals[new_dim_idx];
0296 new_row_idx += rel_idx * old_count;
0297 new_count *= count[new_dim_idx];
0298 }
0299 for (std::size_t col_idx = 0; col_idx < K; ++col_idx) {
0300 data_transposed[col_idx][new_row_idx] = data[row_idx][col_idx];
0301 }
0302 }
0303
0304 std::array<Grid<T, N>, K> grids;
0305 for (std::size_t col_idx = 0; col_idx < K; ++col_idx) {
0306 grids[col_idx] = Grid<T, N>(data_transposed[col_idx].data(), coords);
0307 }
0308 return grids;
0309 }
0310
0311 inline NotEnoughPointsError::NotEnoughPointsError(
0312 std::size_t points,
0313 std::size_t expected_points) :
0314 std::runtime_error(
0315 "Not enough points to construct the grid (found "
0316 + std::to_string(points) + ", expected "
0317 + std::to_string(expected_points) + ")"),
0318 points(points),
0319 expected_points(expected_points) { }
0320
0321 inline SingularDimensionError::SingularDimensionError(std::size_t dim) :
0322 std::runtime_error("Grid is singular in dimension " + std::to_string(dim)),
0323 dim(dim) { }
0324
0325 inline InvalidGridPlanesError::InvalidGridPlanesError() :
0326 std::runtime_error("Lower grid bound must be smaller than upper bound") { }
0327
0328 inline InvalidSpacingError::InvalidSpacingError() :
0329 std::runtime_error("Grid must be spaced uniformly") { }
0330
0331 inline UnexpectedGridPointError::UnexpectedGridPointError(
0332 std::size_t line_number) :
0333 std::runtime_error(
0334 "Unexpected grid point at line " + std::to_string(line_number)),
0335 line_number(line_number) { }
0336
0337 #endif
0338