13#include <casadi/casadi.hpp>
72 std::optional<double> upper = std::nullopt) {
102 return "Interpolator: Hermite/Catmull-Rom is numeric-only because interval selection "
103 "requires runtime comparisons. Use BSpline for symbolic or optimization queries.";
113template <
typename Derived>
116 using Scalar =
typename Derived::Scalar;
119 Eigen::Index idx = 0;
120 for (Eigen::Index j = 0; j < values.cols(); ++j) {
121 for (Eigen::Index i = 0; i < values.rows(); ++i) {
122 flattened(idx++) = values(i, j);
132 return "interpn: symbolic table values are not supported for Hermite/Catmull-Rom "
133 "because interval selection remains numeric-only. Use BSpline for symbolic or "
134 "optimization queries.";
136 return "interpn: symbolic table values are not supported for Nearest because the "
137 "method is non-differentiable. Use Linear or BSpline.";
139 return "interpn: symbolic table values are only supported for Linear and BSpline.";
144 std::vector<std::vector<double>>
grid;
149 const char *context) {
150 if (points.empty()) {
154 const int n_dims =
static_cast<int>(points.size());
156 data.
grid.resize(n_dims);
158 for (
int d = 0; d < n_dims; ++d) {
159 if (points[d].size() < 2) {
161 "] must have at least 2 points");
164 data.
grid[d].resize(points[d].size());
165 Eigen::VectorXd::Map(data.
grid[d].data(), points[d].size()) = points[d];
167 for (
size_t i = 0; i + 1 < data.
grid[d].size(); ++i) {
168 if (data.
grid[d][i + 1] <= data.
grid[d][i]) {
170 "] must be strictly increasing");
181 const char *context) {
182 for (
size_t d = 0; d < grid.size(); ++d) {
183 if (grid[d].size() < 4) {
185 ": BSpline requires at least 4 grid points in dimension " +
191template <JanusScalar Scalar>
193 const char *context) {
194 if (xi.cols() != n_dims && xi.rows() != n_dims) {
196 ": xi must have shape (n_points, n_dims) or (n_dims, n_points)");
199 const bool need_transpose = (xi.rows() == n_dims && xi.cols() != n_dims);
200 if (need_transpose) {
201 return xi.transpose().eval();
206template <JanusScalar Scalar>
208 const std::vector<std::vector<double>> &grid) {
210 for (
int d = 0; d < point.size(); ++d) {
212 std::is_same_v<Scalar, SymbolicScalar> ? point(d) :
SymbolicScalar(point(d));
213 value = SymbolicScalar::fmax(value, grid[d].front());
214 value = SymbolicScalar::fmin(value, grid[d].back());
220template <JanusScalar Scalar>
222 const std::vector<std::vector<double>> &grid) {
223 static_assert(std::is_floating_point_v<Scalar>);
224 for (
int d = 0; d < point.size(); ++d) {
225 if (point(d) < grid[d].front() || point(d) > grid[d].back()) {
233 const std::vector<std::vector<double>> &grid) {
235 for (
int d = 0; d < point.size(); ++d) {
236 out_of_bounds = SymbolicScalar::logic_or(out_of_bounds, point(d) < grid[d].front());
237 out_of_bounds = SymbolicScalar::logic_or(out_of_bounds, point(d) > grid[d].back());
239 return out_of_bounds;
252 opts[
"inline"] =
true;
279 return (y[1] - y[0]) / (x[1] - x[0]);
280 }
else if (i == n - 1) {
282 return (y[n - 1] - y[n - 2]) / (x[n - 1] - x[n - 2]);
285 return (y[i + 1] - y[i - 1]) / (x[i + 1] - x[i - 1]);
314 query = std::max(query, x.front());
315 query = std::min(query, x.back());
318 auto it = std::upper_bound(x.begin(), x.end(), query);
320 if (it == x.begin()) {
322 }
else if (it == x.end()) {
325 idx =
static_cast<size_t>(std::distance(x.begin(), it)) - 1;
332 double x1 = x[idx + 1];
334 double y1 = y[idx + 1];
342 double t = (query - x0) / h;
343 t = std::max(0.0, std::min(1.0, t));
349 double h00 = 2.0 * t3 - 3.0 * t2 + 1.0;
350 double h10 = t3 - 2.0 * t2 + t;
351 double h01 = -2.0 * t3 + 3.0 * t2;
352 double h11 = t3 - t2;
355 return h00 * y0 + h10 * (m0 * h) + h01 * y1 + h11 * (m1 * h);
362 std::vector<int> multi(dims.size());
363 int remaining = flat_idx;
364 for (
size_t d = 0; d < dims.size(); ++d) {
365 multi[d] = remaining % dims[d];
366 remaining /= dims[d];
377 for (
size_t d = 0; d < dims.size(); ++d) {
378 flat += multi[d] * stride;
391 const std::vector<double> &values,
392 const std::vector<double> &query) {
393 int n_dims =
static_cast<int>(grid.size());
396 std::vector<int> dims(n_dims);
397 for (
int d = 0; d < n_dims; ++d) {
398 dims[d] =
static_cast<int>(grid[d].size());
406 std::vector<double> current_values = values;
407 std::vector<int> current_dims = dims;
409 for (
int d = 0; d < n_dims; ++d) {
410 const std::vector<double> &x = grid[d];
411 int n_x = current_dims[0];
414 int remaining_size = 1;
415 for (
size_t dd = 1; dd < current_dims.size(); ++dd) {
416 remaining_size *= current_dims[dd];
420 std::vector<double> new_values(remaining_size);
424 std::vector<double> y_slice(n_x);
425 for (
int i = 0; i < n_x; ++i) {
426 int flat_idx = i +
slice * n_x;
427 y_slice[i] = current_values[flat_idx];
435 current_values = std::move(new_values);
436 current_dims.erase(current_dims.begin());
439 return current_values[0];
491 std::vector<std::vector<double>> m_grid;
492 std::vector<double> m_values;
493 casadi::Function m_casadi_fn;
496 bool m_valid =
false;
497 bool m_use_custom_hermite =
false;
504 std::vector<double> m_slopes_left;
505 std::vector<double> m_slopes_right;
523 if (points.empty()) {
527 m_dims =
static_cast<int>(points.size());
531 m_grid.resize(m_dims);
532 int expected_size = 1;
533 for (
int d = 0; d < m_dims; ++d) {
534 if (points[d].size() < 2) {
538 m_grid[d].resize(points[d].size());
539 Eigen::VectorXd::Map(m_grid[d].data(), points[d].size()) = points[d];
541 if (!std::is_sorted(m_grid[d].begin(), m_grid[d].end())) {
545 expected_size *=
static_cast<int>(points[d].size());
549 if (values.size() != expected_size) {
551 std::to_string(expected_size) +
", got " +
552 std::to_string(values.size()));
555 m_values.resize(values.size());
556 Eigen::VectorXd::Map(m_values.data(), values.size()) = values;
574 if (x.size() != y.size()) {
586 m_grid[0].resize(x.size());
587 Eigen::VectorXd::Map(m_grid[0].data(), x.size()) = x;
589 if (!std::is_sorted(m_grid[0].begin(), m_grid[0].end())) {
593 m_values.resize(y.size());
594 Eigen::VectorXd::Map(m_values.data(), y.size()) = y;
612 m_extrap_config = extrap;
613 compute_boundary_slopes();
635 m_extrap_config = extrap;
636 compute_boundary_slopes();
646 int dims()
const {
return m_dims; }
656 bool valid()
const {
return m_valid; }
669 template <JanusScalar Scalar> Scalar
operator()(
const Scalar &query)
const {
675 if constexpr (std::is_floating_point_v<Scalar>) {
676 return eval_numeric_scalar(query);
678 if (m_use_custom_hermite) {
681 return eval_symbolic_scalar(query);
703 if (query.size() != m_dims) {
705 std::to_string(m_dims) +
", got " +
706 std::to_string(query.size()));
709 if constexpr (std::is_floating_point_v<Scalar>) {
710 return eval_numeric_point(query);
712 if (m_use_custom_hermite) {
715 return eval_symbolic_point(query);
733 template <
typename Derived>
734 auto operator()(
const Eigen::MatrixBase<Derived> &queries)
const
736 using Scalar =
typename Derived::Scalar;
744 n_points =
static_cast<int>(queries.size());
747 bool is_transposed = (queries.rows() == m_dims && queries.cols() != m_dims);
749 is_transposed ?
static_cast<int>(queries.cols()) :
static_cast<int>(queries.rows());
754 if constexpr (std::is_floating_point_v<Scalar>) {
757 for (
int i = 0; i < n_points; ++i) {
760 if (queries.cols() == 1) {
765 result(i) = eval_numeric_scalar(val);
769 bool is_transposed = (queries.rows() == m_dims && queries.cols() != m_dims);
770 for (
int i = 0; i < n_points; ++i) {
772 for (
int d = 0; d < m_dims; ++d) {
773 point(d) = is_transposed ? queries(d, i) : queries(i, d);
775 result(i) = eval_numeric_point(point);
779 if (m_use_custom_hermite) {
783 for (
int i = 0; i < n_points; ++i) {
785 if (queries.cols() == 1) {
790 result(i) = eval_symbolic_scalar(val);
793 bool is_transposed = (queries.rows() == m_dims && queries.cols() != m_dims);
794 for (
int i = 0; i < n_points; ++i) {
796 for (
int d = 0; d < m_dims; ++d) {
797 point(d) = is_transposed ? queries(d, i) : queries(i, d);
799 result(i) = eval_symbolic_point(point);
815 m_use_custom_hermite =
true;
819 for (
int d = 0; d < m_dims; ++d) {
820 if (m_grid[d].size() < 4) {
821 throw InterpolationError(
822 "Interpolator: BSpline requires at least 4 grid points in dimension " +
826 m_casadi_fn = casadi::interpolant(
"interp",
"bspline", m_grid, m_values);
830 m_casadi_fn = casadi::interpolant(
"interp",
"linear", m_grid, m_values);
834 m_casadi_fn = casadi::interpolant(
"interp",
"linear", m_grid, m_values);
845 void compute_boundary_slopes() {
850 m_slopes_left.resize(m_dims, 0.0);
851 m_slopes_right.resize(m_dims, 0.0);
854 for (
int d = 0; d < m_dims; ++d) {
855 const auto &grid_d = m_grid[d];
856 size_t n_d = grid_d.size();
860 double x_min = grid_d.front();
861 double x_max = grid_d.back();
862 double h_left = grid_d[1] - grid_d[0];
863 double h_right = grid_d[n_d - 1] - grid_d[n_d - 2];
866 std::vector<double> query_base(m_dims);
867 for (
int dd = 0; dd < m_dims; ++dd) {
869 query_base[dd] = 0.5 * (m_grid[dd].front() + m_grid[dd].back());
873 query_base[d] = x_min;
874 std::vector<casadi::DM> args0 = {casadi::DM(query_base)};
875 double f0 =
static_cast<double>(m_casadi_fn(args0)[0]);
877 query_base[d] = x_min + h_left;
878 std::vector<casadi::DM> args1 = {casadi::DM(query_base)};
879 double f1 =
static_cast<double>(m_casadi_fn(args1)[0]);
881 m_slopes_left[d] = (f1 - f0) / h_left;
884 query_base[d] = x_max - h_right;
885 std::vector<casadi::DM> args2 = {casadi::DM(query_base)};
886 double f2 =
static_cast<double>(m_casadi_fn(args2)[0]);
888 query_base[d] = x_max;
889 std::vector<casadi::DM> args3 = {casadi::DM(query_base)};
890 double f3 =
static_cast<double>(m_casadi_fn(args3)[0]);
892 m_slopes_right[d] = (f3 - f2) / h_right;
899 double apply_output_bounds(
double value)
const {
900 if (m_extrap_config.lower_bound.has_value()) {
901 value = std::max(value, m_extrap_config.lower_bound.value());
903 if (m_extrap_config.upper_bound.has_value()) {
904 value = std::min(value, m_extrap_config.upper_bound.value());
914 if (m_extrap_config.lower_bound.has_value()) {
915 result = SymbolicScalar::fmax(result, m_extrap_config.lower_bound.value());
917 if (m_extrap_config.upper_bound.has_value()) {
918 result = SymbolicScalar::fmin(result, m_extrap_config.upper_bound.value());
927 double eval_numeric_scalar(
double query)
const {
928 const double x_min = m_grid[0].front();
929 const double x_max = m_grid[0].back();
935 double result = m_values.front() + m_slopes_left[0] * (query - x_min);
936 return apply_output_bounds(result);
937 }
else if (query > x_max) {
939 double result = m_values.back() + m_slopes_right[0] * (query - x_max);
940 return apply_output_bounds(result);
946 double clamped = std::max(query, x_min);
947 clamped = std::min(clamped, x_max);
950 if (m_use_custom_hermite) {
954 auto it = std::lower_bound(m_grid[0].begin(), m_grid[0].end(), clamped);
955 size_t idx = std::distance(m_grid[0].begin(), it);
956 if (idx > 0 && (idx == m_grid[0].size() || std::abs(clamped - m_grid[0][idx - 1]) <
957 std::abs(clamped - m_grid[0][idx]))) {
960 result = m_values[idx];
963 std::vector<casadi::DM> args = {casadi::DM(clamped)};
964 std::vector<casadi::DM> res = m_casadi_fn(args);
965 result =
static_cast<double>(res[0]);
968 return apply_output_bounds(result);
971 double eval_numeric_point(
const NumericVector &query)
const {
973 double extrap_correction = 0.0;
974 bool needs_extrapolation =
false;
977 for (
int d = 0; d < m_dims; ++d) {
978 double val = query(d);
979 double x_min = m_grid[d].front();
980 double x_max = m_grid[d].back();
984 extrap_correction += m_slopes_left[d] * (val - x_min);
985 needs_extrapolation =
true;
986 }
else if (val > x_max) {
988 extrap_correction += m_slopes_right[d] * (val - x_max);
989 needs_extrapolation =
true;
995 std::vector<double> clamped(m_dims);
996 for (
int d = 0; d < m_dims; ++d) {
997 double val = query(d);
998 val = std::max(val, m_grid[d].front());
999 val = std::min(val, m_grid[d].back());
1004 if (m_use_custom_hermite) {
1009 std::vector<casadi::DM> args = {casadi::DM(clamped)};
1010 std::vector<casadi::DM> res = m_casadi_fn(args);
1011 result =
static_cast<double>(res[0]);
1014 std::vector<casadi::DM> args = {casadi::DM(clamped)};
1015 std::vector<casadi::DM> res = m_casadi_fn(args);
1016 result =
static_cast<double>(res[0]);
1020 if (needs_extrapolation) {
1021 result += extrap_correction;
1024 return apply_output_bounds(result);
1032 const double x_min = m_grid[0].front();
1033 const double x_max = m_grid[0].back();
1037 clamped = SymbolicScalar::fmin(clamped, x_max);
1040 std::vector<casadi::MX> args = {clamped};
1041 std::vector<casadi::MX> res = m_casadi_fn(args);
1047 SymbolicScalar left_extrap = m_values.front() + m_slopes_left[0] * (query - x_min);
1050 SymbolicScalar right_extrap = m_values.back() + m_slopes_right[0] * (query - x_max);
1054 query < x_min, left_extrap,
1055 casadi::MX::if_else(query > x_max, right_extrap, interp_result));
1057 return apply_output_bounds_sym(result);
1060 return apply_output_bounds_sym(interp_result);
1065 casadi::MX clamped(m_dims, 1);
1066 for (
int d = 0; d < m_dims; ++d) {
1067 casadi::MX val = query(d);
1068 val = casadi::MX::fmax(val, m_grid[d].front());
1069 val = casadi::MX::fmin(val, m_grid[d].back());
1074 std::vector<casadi::MX> args = {clamped};
1075 std::vector<casadi::MX> res = m_casadi_fn(args);
1083 for (
int d = 0; d < m_dims; ++d) {
1084 double x_min = m_grid[d].front();
1085 double x_max = m_grid[d].back();
1086 casadi::MX val = query(d);
1090 SymbolicScalar left_contrib = casadi::MX::if_else(val < x_min, left_corr, 0.0);
1094 SymbolicScalar right_contrib = casadi::MX::if_else(val > x_max, right_corr, 0.0);
1096 extrap_correction = extrap_correction + left_contrib + right_contrib;
1099 return apply_output_bounds_sym(interp_result + extrap_correction);
1102 return apply_output_bounds_sym(interp_result);
1126template <
typename Scalar>
1130 std::optional<Scalar> fill_value = std::nullopt) {
1132 const int n_dims =
static_cast<int>(points.size());
1134 const int n_points =
static_cast<int>(xi_work.rows());
1137 if (values_flat.size() != grid_data.expected_size) {
1139 std::to_string(grid_data.expected_size) +
", got " +
1140 std::to_string(values_flat.size()));
1150 if (fill_value.has_value()) {
1151 for (
int i = 0; i < n_points; ++i) {
1152 bool out_of_bounds =
false;
1153 if constexpr (std::is_floating_point_v<Scalar>) {
1158 if (out_of_bounds) {
1159 result(i) = fill_value.value();
1162 result(i) = interp(point);
1167 for (
int i = 0; i < n_points; ++i) {
1169 result(i) = interp(point);
1191template <
typename Scalar>
1195 std::optional<SymbolicScalar> fill_value = std::nullopt) {
1197 const int n_dims =
static_cast<int>(points.size());
1199 const int n_points =
static_cast<int>(xi_work.rows());
1201 if (values_flat.size() != grid_data.expected_size) {
1203 std::to_string(grid_data.expected_size) +
", got " +
1204 std::to_string(values_flat.size()));
1211 for (
int i = 0; i < n_points; ++i) {
1214 SymbolicScalar interp_value = interp(std::vector<SymbolicScalar>{clamped_point, coeffs})[0];
1216 if (fill_value.has_value()) {
1217 if constexpr (std::is_floating_point_v<Scalar>) {
1219 ? fill_value.value()
1225 SymbolicScalar::if_else(out_of_bounds, fill_value.value(), interp_value);
1228 result(i) = interp_value;
C++20 concepts constraining valid Janus scalar types.
Custom exception hierarchy for Janus framework.
Core type aliases for numeric and symbolic Eigen/CasADi interop.
Linear algebra operations (solve, inverse, determinant, eigendecomposition, norms).
Interpolation-specific errors.
Definition JanusError.hpp:51
Definition Interpolate.hpp:489
Interpolator(const std::vector< NumericVector > &points, const NumericVector &values, InterpolationMethod method=InterpolationMethod::Linear)
Construct N-dimensional interpolator.
Definition Interpolate.hpp:521
Scalar operator()(const Scalar &query) const
Evaluate interpolant at a scalar point (1D only).
Definition Interpolate.hpp:669
Interpolator(const NumericVector &x, const NumericVector &y, InterpolationMethod method=InterpolationMethod::Linear)
Construct 1D interpolator (convenience overload).
Definition Interpolate.hpp:572
Interpolator(const std::vector< NumericVector > &points, const NumericVector &values, InterpolationMethod method, ExtrapolationConfig extrap)
Construct N-dimensional interpolator with extrapolation config.
Definition Interpolate.hpp:609
InterpolationMethod method() const
Get the interpolation method.
Definition Interpolate.hpp:651
bool valid() const
Check if interpolator is valid (initialized).
Definition Interpolate.hpp:656
Scalar operator()(const JanusVector< Scalar > &query) const
Evaluate N-D interpolant at a single point.
Definition Interpolate.hpp:699
auto operator()(const Eigen::MatrixBase< Derived > &queries) const -> JanusVector< typename Derived::Scalar >
Evaluate interpolant at multiple points.
Definition Interpolate.hpp:734
Interpolator()=default
Default constructor (invalid state).
int dims() const
Get number of dimensions.
Definition Interpolate.hpp:646
Interpolator(const NumericVector &x, const NumericVector &y, InterpolationMethod method, ExtrapolationConfig extrap)
Definition Interpolate.hpp:632
auto slice(const Eigen::MatrixBase< Derived > &m, Eigen::Index start, Eigen::Index end)
Definition IntegrateDiscrete.hpp:32
JanusVector< typename Derived::Scalar > flatten_fortran_order(const Eigen::MatrixBase< Derived > &values)
Flatten N-D values array in Fortran order for CasADi.
Definition Interpolate.hpp:115
std::string method_to_casadi_string(InterpolationMethod method)
Convert InterpolationMethod enum to CasADi string.
Definition Interpolate.hpp:86
double hermite_interp_1d_numeric(const std::vector< double > &x, const std::vector< double > &y, double query)
Evaluate 1D Catmull-Rom cubic Hermite spline (numeric only).
Definition Interpolate.hpp:306
double hermite_interpn_numeric(const std::vector< std::vector< double > > &grid, const std::vector< double > &values, const std::vector< double > &query)
N-dimensional Catmull-Rom interpolation via tensor product (numeric only).
Definition Interpolate.hpp:390
int multi_to_flat_index(const std::vector< int > &multi, const std::vector< int > &dims)
Compute flat index from multi-dimensional index (Fortran order).
Definition Interpolate.hpp:374
casadi::Function make_parametric_interpolant(const std::vector< std::vector< double > > &grid, InterpolationMethod method)
Definition Interpolate.hpp:242
bool point_out_of_bounds_numeric(const JanusVector< Scalar > &point, const std::vector< std::vector< double > > &grid)
Definition Interpolate.hpp:221
double catmull_rom_slope(const std::vector< double > &x, const std::vector< double > &y, size_t i)
Compute Catmull-Rom slope at a point using neighboring values.
Definition Interpolate.hpp:271
void validate_bspline_grid(const std::vector< std::vector< double > > &grid, const char *context)
Definition Interpolate.hpp:180
InterpnGridData build_interpn_grid(const std::vector< NumericVector > &points, const char *context)
Definition Interpolate.hpp:148
std::vector< int > flat_to_multi_index(int flat_idx, const std::vector< int > &dims)
Compute multi-dimensional index from flat index (Fortran order).
Definition Interpolate.hpp:361
const char * hermite_symbolic_error_message()
Definition Interpolate.hpp:101
JanusMatrix< Scalar > normalize_query_matrix(const JanusMatrix< Scalar > &xi, int n_dims, const char *context)
Definition Interpolate.hpp:192
SymbolicScalar point_out_of_bounds_symbolic(const SymbolicVector &point, const std::vector< std::vector< double > > &grid)
Definition Interpolate.hpp:232
SymbolicScalar clamp_query_point(const JanusVector< Scalar > &point, const std::vector< std::vector< double > > &grid)
Definition Interpolate.hpp:207
std::string symbolic_values_method_error_message(InterpolationMethod method)
Definition Interpolate.hpp:129
Definition Diagnostics.hpp:19
JanusVector< SymbolicScalar > SymbolicVector
Eigen vector of MX elements.
Definition JanusTypes.hpp:72
InterpolationMethod
Supported interpolation methods.
Definition Interpolate.hpp:29
@ Linear
Piecewise linear (C0 continuous) - fast, simple.
Definition Interpolate.hpp:30
@ Nearest
Nearest neighbor - fast, non-differentiable.
Definition Interpolate.hpp:33
@ Hermite
Cubic Hermite/Catmull-Rom (C1 continuous) - smooth gradients.
Definition Interpolate.hpp:31
@ BSpline
Cubic B-spline (C2 continuous) - smoothest, good for optimization.
Definition Interpolate.hpp:32
Eigen::Matrix< Scalar, Eigen::Dynamic, Eigen::Dynamic > JanusMatrix
Dynamic-size matrix for both numeric and symbolic backends.
Definition JanusTypes.hpp:43
ExtrapolationMode
Controls behavior when query points fall outside grid bounds.
Definition Interpolate.hpp:43
@ Clamp
Clamp queries to grid bounds (default, safe).
Definition Interpolate.hpp:44
@ Linear
Linear extrapolation from boundary slope (1D only).
Definition Interpolate.hpp:45
JanusVector< Scalar > interpn(const std::vector< NumericVector > &points, const NumericVector &values_flat, const JanusMatrix< Scalar > &xi, InterpolationMethod method=InterpolationMethod::Linear, std::optional< Scalar > fill_value=std::nullopt)
N-dimensional interpolation on regular grids (backwards compatibility).
Definition Interpolate.hpp:1127
JanusVector< NumericScalar > NumericVector
Eigen::VectorXd equivalent.
Definition JanusTypes.hpp:67
@ Hermite
Definition AutoDiff.hpp:31
Eigen::Matrix< Scalar, Eigen::Dynamic, 1 > JanusVector
Dynamic-size column vector for both numeric and symbolic backends.
Definition JanusTypes.hpp:49
casadi::MX SymbolicScalar
CasADi MX symbolic scalar.
Definition JanusTypes.hpp:70
SymbolicScalar as_mx(const SymbolicVector &v)
Get the underlying MX representation of a SymbolicVector.
Definition JanusTypes.hpp:173
Definition Interpolate.hpp:143
std::vector< std::vector< double > > grid
Definition Interpolate.hpp:144
int expected_size
Definition Interpolate.hpp:145