diff --git a/stan/math/prim/fun.hpp b/stan/math/prim/fun.hpp index 0abd350d248..89c7eb715db 100644 --- a/stan/math/prim/fun.hpp +++ b/stan/math/prim/fun.hpp @@ -47,6 +47,7 @@ #include #include #include +#include #include #include #include @@ -137,6 +138,8 @@ #include #include #include +#include +#include #include #include #include diff --git a/stan/math/prim/fun/conv_gaus_line.hpp b/stan/math/prim/fun/conv_gaus_line.hpp new file mode 100644 index 00000000000..cf8738d8d68 --- /dev/null +++ b/stan/math/prim/fun/conv_gaus_line.hpp @@ -0,0 +1,47 @@ +#ifndef STAN_MATH_PRIM_FUN_CONV_GAUS_LINE +#define STAN_MATH_PRIM_FUN_CONV_GAUS_LINE + +#include +#include +#include +#include + +namespace stan { +namespace math { + +/** + * Evaluate the convolution of a line with a Gaussian kernel on an interval. + * + * \f$\int_{t_0}^{t_1} (at + b) e^{\frac{-(t-x)^2}{2\sigma^2}} dt \f$ + * + * @tparam Tx type of x + * @param t0 lower integration bound + * @param t1 upper integration bound + * @param a coefficient of t in line + * @param b constant in line + * @param x point at which convolution is evaluated + * @param sig2 variance of the Gaussian kernel + * @return The value of the derivative + */ +template +inline return_type_t conv_gaus_line(double t0, double t1, double a, + double b, const Tx& x, double sig2) { + using stan::math::normal_cdf; + using std::exp; + using std::pow; + using std::sqrt; + const double sig = sqrt(sig2); + + return_type_t y + = (a * x + b) * (normal_cdf(t1, x, sig) - normal_cdf(t0, x, sig)); + y += -a * sig2 / sqrt(2 * stan::math::pi() * sig2) + * (exp(-pow(t1 - x, 2) / (2 * sig2)) + - exp(-pow(t0 - x, 2) / (2 * sig2))); + + return y; +} + +} // namespace math +} // namespace stan + +#endif diff --git a/stan/math/prim/fun/interp_gauss.hpp b/stan/math/prim/fun/interp_gauss.hpp new file mode 100644 index 00000000000..5f39ff5fd0a --- /dev/null +++ b/stan/math/prim/fun/interp_gauss.hpp @@ -0,0 +1,197 @@ +#ifndef STAN_MATH_PRIM_FUN_INTERP_GAUSS +#define STAN_MATH_PRIM_FUN_INTERP_GAUSS + +#include +#include +#include +#include +#include +#include + +namespace stan { +namespace math { + +namespace internal { +/* + * find the smallest difference between successive elements in a sorted vector + */ +template +inline double min_diff(const std::vector& xs) { + double dmin = value_of(xs[1]) - value_of(xs[0]); + int N = xs.size(); + for (int i = 1; i < N - 1; i++) { + if (value_of(xs[i + 1]) - value_of(xs[i]) < dmin) { + dmin = value_of(xs[i + 1]) - value_of(xs[i]); + } + } + return dmin; +} + +} // namespace internal + +/** + * Given a set of reference points \f$(xs_i, ys_i)\f$, create a mollifier + * that intersects the reference points. This function requires as input + * a vector, params, created by the function interp_gauss_precomp. The + * algorithm used to create the mollifier is an iterative algorithm that + * works as follows. First a linear interpolation is created through the + * reference points. Then, the + * linear interpolation is convolved with a Gaussian whose width is + * proportional the smallest distance between successive points + * \f$xs_i\f$ and \f$xs_{i+1}\f$. Since the convolution is unlikely to + * intersect the reference points, the y-values of the reference points + * are shifted and the process repeats itself until the interpolation + * intersects all reference points. + * + * Note: This interpolation scheme should be used when the function + * to be interpolated is well-resolved by the reference points. + * + * @tparam Tx type of x + * @param xs vector of independent variable of reference points + * @param ys vector of dependent variable of reference points + * @param params a vector created by interp_gauss_precomp + * @param x the point at which to evaluate the interpolation + * @return value of the interpolation at x + */ +template +inline return_type_t interp_gauss(const std::vector& xs, + const std::vector& ys, + const std::vector& params, + const Tx& x) { + // enforce that interpolation point is between smallest and largest + // reference point + static char const* function = "interp_gauss"; + check_less_or_equal(function, "Interpolation point", x, xs.back()); + check_greater_or_equal(function, "Interpolation point", x, xs.front()); + check_ordered(function, "xs", xs); + check_not_nan(function, "xs", xs); + check_not_nan(function, "ys", ys); + check_not_nan(function, "x", x); + check_greater(function, "xs", xs.size(), 1); + + // number of standard deviations to extend endpoints for convolution + const double NSTDS = 10; + int N = xs.size(); + + // params is vector of the form (as, bs, sig2) + + // create copy of xs so that endpoints can be extended + std::vector xs2 = xs; + + // extend out first and last lines for convolution + double sig = std::sqrt(params[2 * N - 2]); + xs2[0] = xs[0] - NSTDS * sig; + xs2[N - 1] = xs[N - 1] + NSTDS * sig; + + // no need to convolve far from center of gaussian, so + // get lower and upper indexes for integration bounds + auto lb = std::lower_bound(xs.begin(), xs.end(), x - NSTDS * sig); + int ind_start = std::distance(xs.begin(), lb) - 1; + ind_start = std::max(0, ind_start); + + auto ub = std::upper_bound(xs.begin(), xs.end(), x + NSTDS * sig); + int ind_end = std::distance(xs.begin(), ub); + ind_end = std::min(N - 1, ind_end); + + // sum convolutions over intervals + return_type_t y = 0; + for (int i = ind_start; i < ind_end; i++) { + y += conv_gaus_line(xs2[i], xs2[i + 1], params[i], params[(N - 1) + i], x, + params[2 * N - 2]); + } + return y; +} + +/** + * This function was written to be used with interp_gauss. This function + * computes the shifted y-values of the reference points of an interpolation + * in such a way that when that piecewise linear function is convolved + * with a Gaussian kernel, the resulting function coincides with the + * points \f$(xs_i, ys_i)\f$ inputted into this function. The output of this + * function depends heavily on the choice of width of the Gaussian + * kernel, which at the time of writing, is set to one tenth the + * minimum distance between successive elements of the vector xs. + * A tolerance for the maximum distance between the interpolation and + * all reference points is also set manually and is not an input. + * + * @param xs vector of independent variable of reference points + * @param ys vector of dependent variable of reference points + * @return vector containing slopes, intercepts, and width of kernel + */ +inline std::vector interp_gauss_precomp(const std::vector& xs, + const std::vector& ys) { + static char const* function = "interp_gauss_precomp"; + check_not_nan(function, "xs", xs); + check_not_nan(function, "ys", ys); + check_ordered(function, "xs", xs); + check_greater(function, "xs", xs.size(), 1); + + using internal::min_diff; + static const double max_diff = 1e-8; + // set Gaussian kernel to sig2_scale times smallest difference between + // successive points + static const double sig2_scale = 0.1; + int N = xs.size(); + + // create the vector to be returned that consists of as, bs, sig2 + std::vector params(2 * N - 1, 0.0); + params[2 * N - 2] = square(min_diff(xs) * sig2_scale); + + // copy ys into a new vector that will be changed + std::vector y2s = ys; + + // interatively find interpolation that coincides with ys at xs + int max_iters = 100; + double dd; + for (int j = 0; j < max_iters; j++) { + // find slope and intercept of line between each point + for (size_t i = 0; i < N - 1; i++) { + params[i] = (y2s[i + 1] - y2s[i]) / (xs[i + 1] - xs[i]); + params[(N - 1) + i] = -xs[i] * params[i] + y2s[i]; + } + + double dmax = 0; + for (size_t i = 0; i < N; i++) { + dd = ys[i] - interp_gauss(xs, y2s, params, xs[i]); + y2s[i] += dd; + dmax = std::max(std::abs(dd), dmax); + } + if (dmax < max_diff) { + break; + } + } + return params; +} + +/** + * This function combines interp_gauss_precomp and interp_gauss. + * It takes as input two vectors of reference points (xs and ys) + * in addition to a vector, xs_new, of points at which this + * function will evaluate the interpolation at those reference + * points. + * + * @tparam Tx type of xs_new + * @param xs vector of independent variable of reference points + * @param ys vector of dependent variable of reference points + * @param xs_new vector of point at which to evaluate interpolation + * @return vector of interpolation values + */ +template +inline std::vector interp_gauss(const std::vector& xs, + const std::vector& ys, + const std::vector& xs_new) { + int n_interp = xs_new.size(); + std::vector ys_new(n_interp); + + // create interpolation + std::vector params = interp_gauss_precomp(xs, ys); + for (int i = 0; i < n_interp; i++) { + ys_new[i] = interp_gauss(xs, ys, params, xs_new[i]); + } + return ys_new; +} + +} // namespace math +} // namespace stan + +#endif diff --git a/stan/math/prim/fun/interp_lin.hpp b/stan/math/prim/fun/interp_lin.hpp new file mode 100644 index 00000000000..1decb8f36a5 --- /dev/null +++ b/stan/math/prim/fun/interp_lin.hpp @@ -0,0 +1,91 @@ +#ifndef STAN_MATH_PRIM_FUN_INTERP_LIN +#define STAN_MATH_PRIM_FUN_INTERP_LIN + +#include +#include +#include +#include + +namespace stan { +namespace math { + +/** + * This function performs linear interpolation. The function takes as + * input two vectors of reference points \f$(xs_i, ys_i)\f$ in addition + * to one value, \f$x\f$, at which the value of the linear interpolation + * is to be returned. The vector xs is required to be in increasing order + * For values of \f$x\f$ less than xs[0], the function returns ys[0]. + * For values of \f$x\f$ greater than xs[n-1], the function returns + * ys[n-1], where xs is of length n. + * + * @param xs vector of independent variable of reference points + * @param ys vector of dependent variable of reference points + * @param x the point at which to evaluate the interpolation + * @return value of linear interpolation at x + */ +inline double interp_lin(const std::vector& xs, + const std::vector& ys, double x) { + static char const* function = "interp_lin"; + check_ordered(function, "xs", xs); + check_not_nan(function, "xs", xs); + check_not_nan(function, "ys", ys); + check_not_nan(function, "x", x); + check_greater(function, "xs", xs.size(), 1); + int n = xs.size(); + + // if x is less than left endpoint or greater than right, return endpoint + if (x <= xs[0]) { + return ys[0]; + } + if (x >= xs[n - 1]) { + return ys[n - 1]; + } + + // find in between which points the input, x, lives + auto ub = std::upper_bound(xs.begin(), xs.end(), x); + int ind = std::distance(xs.begin(), ub); + + // check if the interpolation point falls on a reference point + if (x == xs[ind - 1]) { + return ys[ind - 1]; + } + + // do linear interpolation + double x1 = xs[ind - 1]; + double x2 = xs[ind]; + double y1 = ys[ind - 1]; + double y2 = ys[ind]; + + return y1 + (x - x1) * (y2 - y1) / (x2 - x1); +} + +/** + * This function takes as input two vectors of reference points (xs and ys) + * in addition to a vector, xs_new, of points at which this + * function will evaluate a linear interpolation through those reference + * points. + * + * @param xs vector of independent variable of reference points + * @param ys vector of dependent variable of reference points + * @param xs_new vector of point at which to evaluate interpolation + * @return vector of interpolation values + */ + +template +inline std::vector interp_lin(const std::vector& xs, + const std::vector& ys, + const std::vector& xs_new) { + int n_interp = xs_new.size(); + std::vector ys_new(n_interp); + + // create interpolation + for (int i = 0; i < n_interp; i++) { + ys_new[i] = interp_lin(xs, ys, xs_new[i]); + } + return ys_new; +} + +} // namespace math +} // namespace stan + +#endif diff --git a/stan/math/rev/fun.hpp b/stan/math/rev/fun.hpp index 617ff6966aa..d23048de56a 100644 --- a/stan/math/rev/fun.hpp +++ b/stan/math/rev/fun.hpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include diff --git a/stan/math/rev/fun/conv_gaus_line.hpp b/stan/math/rev/fun/conv_gaus_line.hpp new file mode 100644 index 00000000000..640b367bcd6 --- /dev/null +++ b/stan/math/rev/fun/conv_gaus_line.hpp @@ -0,0 +1,78 @@ +#ifndef STAN_MATH_REV_FUN_CONV_GAUS_LINE +#define STAN_MATH_REV_FUN_CONV_GAUS_LINE + +#include +#include +#include + +namespace stan { +namespace math { + +namespace internal { + +/** \ingroup prob_dists + * Evaluates the derivative of the convolution of a line with a Gaussian + * kernel on an interval. + * + * \f$\frac{\partial}{\partial x} \int_{t_0}^{t_1} (at + b) \f$ + * \f$ e^{\frac{-(t-x)^2}{2\sigma^2}} dt \f$ + * + * @param t0 lower integration bound + * @param t1 upper integration bound + * @param a coefficient of t in line + * @param b constant in line + * @param x0 point at which convolution is evaluated + * @param sig2 variance of the Gaussian kernel + * @return The value of the derivative + */ +inline double der_conv_gaus_line(double t0, double t1, double a, double b, + double x0, double sig2) { + using stan::math::normal_cdf; + using std::exp; + using std::pow; + using std::sqrt; + const double sig = sqrt(sig2); + const double alpha = sqrt(2 * pi() * sig2); + + double term1 = exp(-pow(t1 - x0, 2) / (2 * sig2)); + double term2 = exp(-pow(t0 - x0, 2) / (2 * sig2)); + double y = (a * x0 + b) / alpha * (-term1 + term2); + y += a * (normal_cdf(t1, x0, sig) - normal_cdf(t0, x0, sig)); + y -= a / alpha * ((t1 - x0) * term1 - (t0 - x0) * term2); + return y; +} + +class conv_gaus_line_vari : public op_v_vari { + double t0_; + double t1_; + double a_; + double b_; + double sig2_; + + public: + explicit conv_gaus_line_vari(double t0, double t1, double a, double b, + vari* avi, double sig2) + : op_v_vari(conv_gaus_line(t0, t1, a, b, avi->val_, sig2), avi), + t0_(t0), + t1_(t1), + a_(a), + b_(b), + sig2_(sig2) {} + + void chain() { + avi_->adj_ + += adj_ * der_conv_gaus_line(t0_, t1_, a_, b_, avi_->val_, sig2_); + } +}; + +} // namespace internal + +inline var conv_gaus_line(double t0, double t1, double a, double b, + const var& x, double sig2) { + return var(new internal::conv_gaus_line_vari(t0, t1, a, b, x.vi_, sig2)); +} + +} // namespace math +} // namespace stan + +#endif diff --git a/test/unit/math/mix/fun/conv_gaus_line_test.cpp b/test/unit/math/mix/fun/conv_gaus_line_test.cpp new file mode 100644 index 00000000000..7124bd2cf2c --- /dev/null +++ b/test/unit/math/mix/fun/conv_gaus_line_test.cpp @@ -0,0 +1,35 @@ +#include +#include +#include + +/* +check derivative using finite differences and autodiffing +*/ +TEST(mathMixGausInterp, gaus_conv_der) { + double t0, t1, a, b, x0, sig2, y, h, yn, yp, x0p, x0n, dder, dder2; + double alpha, sig; + using stan::math::conv_gaus_line; + + t0 = 0; + t1 = 5; + a = -1; + b = 2; + x0 = 0.99; + sig2 = 2; + + // derivative with finite difference + h = 1e-4; + x0n = x0 + h; + x0p = x0 - h; + yn = conv_gaus_line(t0, t1, a, b, x0n, sig2); + yp = conv_gaus_line(t0, t1, a, b, x0p, sig2); + dder = (yn - yp) / (2 * h); + + // derivative with autodiff + using stan::math::var; + var v = x0; + var vy = conv_gaus_line(t0, t1, a, b, v, sig2); + vy.grad(); + + ASSERT_NEAR(dder, v.adj(), 1e-5); +} diff --git a/test/unit/math/mix/fun/interp_gauss_test.cpp b/test/unit/math/mix/fun/interp_gauss_test.cpp new file mode 100644 index 00000000000..456ff0c8f92 --- /dev/null +++ b/test/unit/math/mix/fun/interp_gauss_test.cpp @@ -0,0 +1,68 @@ +#include + +TEST(mathMixGausInterp, derivs) { + using stan::math::interp_gauss; + using stan::math::var; + std::vector xs, ys, ts; + double xmin, xmax, x, y, t, t0, t1, dder, dder2, x0, x1; + int n; + + // generate function tabulation + n = 5; + xmin = 0; + xmax = 1; + xs.resize(n); + ys.resize(n); + for (int i = 0; i < n; i++) { + x = xmin + i * (xmax - xmin) / (n - 1); + xs[i] = x; + } + ys[0] = 0; + ys[1] = 0; + ys[2] = 1; + ys[3] = 0; + ys[4] = 0.01; + + // create vector of interpolation pts + std::vector xs_new_v; + std::vector xs_new; + int n_interp = 100; + + // add a cushion to each endpoint so that we don't leave interval + // when taking finite differences + t0 = xs[0] + 0.01; + t1 = xmax - 0.01; + for (int i = 0; i < n_interp; i++) { + t = t0 + i * (t1 - t0) / (n_interp - 1); + xs_new.push_back(t); + stan::math::var tvar = t; + xs_new_v.push_back(tvar); + } + + std::vector ys_new, ys_new_p, ys_new_n, xs_new_p, xs_new_n; + ys_new = interp_gauss(xs, ys, xs_new); + + // autodiff at each interpolation pt + std::vector ys_new_v; + ys_new_v = interp_gauss(xs, ys, xs_new_v); + + std::vector ys_new_dder; + for (int i = 0; i < n_interp; i++) { + ys_new_v[i].grad(); + dder = xs_new_v[i].adj(); + ys_new_dder.push_back(dder); + } + + // take derivative of interpolation using finite differencing + double h = 1e-6; + xs_new_p = xs_new; + xs_new_n = xs_new; + for (int i = 0; i < n_interp; i++) { + xs_new_p[i] += -h; + xs_new_n[i] += h; + ys_new_p = interp_gauss(xs, ys, xs_new_p); + ys_new_n = interp_gauss(xs, ys, xs_new_n); + dder2 = (ys_new_n[i] - ys_new_p[i]) / (2 * h); + ASSERT_NEAR(ys_new_dder[i], dder2, 1e-5); + } +} diff --git a/test/unit/math/prim/fun/conv_gaus_line_test.cpp b/test/unit/math/prim/fun/conv_gaus_line_test.cpp new file mode 100644 index 00000000000..67a80cfcc3d --- /dev/null +++ b/test/unit/math/prim/fun/conv_gaus_line_test.cpp @@ -0,0 +1,53 @@ +#include +#include +#include + +/* +evaluate the convolution using trapezoid rule +*/ +double check_int_dumb(double t0, double t1, double a, double b, double x0, + double sig2) { + double x, y, t, tot; + std::vector ys, ts; + double pi = stan::math::pi(); + int n; + + // evaluate function at equispaced nodes + n = 100000; + for (int i = 0; i < n; i++) { + t = t0 + i * (t1 - t0) / (n - 1); + ts.push_back(t); + + y = a * t + b; + y = y * exp(-pow(t - x0, 2) / (2 * sig2)); + ys.push_back(y); + } + + // sum up function evaluations + tot = 0; + for (int i = 0; i < n; i++) { + tot += ys[i]; + } + tot *= (t1 - t0) / n; + tot /= sqrt(2 * pi * sig2); + + return tot; +} + +/* +check that integral using formula is close to trapezoid approximation +*/ +TEST(mathMixConvGausLin, trap_test) { + double t0, t1, a, b, x0, sig2, y, y2; + using stan::math::conv_gaus_line; + t0 = 0; + t1 = 1; + a = 1; + b = 0; + x0 = 1; + sig2 = 3; + y = check_int_dumb(t0, t1, a, b, x0, sig2); + y2 = conv_gaus_line(t0, t1, a, b, x0, sig2); + + ASSERT_NEAR(y, y2, 1e-5); +} diff --git a/test/unit/math/prim/fun/interp_gauss_test.cpp b/test/unit/math/prim/fun/interp_gauss_test.cpp new file mode 100644 index 00000000000..efdce502dfc --- /dev/null +++ b/test/unit/math/prim/fun/interp_gauss_test.cpp @@ -0,0 +1,180 @@ +#include +#include +#include + +double ABS_TOL = 1e-12; + +TEST(mathPrimInterpGauss, throwing) { + using stan::math::interp_gauss; + using stan::math::interp_gauss_precomp; + double nan = std::numeric_limits::quiet_NaN(); + double x; + std::vector xs, ys; + + // check that when xs are not increasing, an error throws + int n = 2; + xs = {1, 1}; + ys = {0, 2}; + EXPECT_THROW(interp_gauss_precomp(xs, ys), std::domain_error); + + // check that when xs contain a nan + xs = {nan, 1}; + ys = {0, 2}; + EXPECT_THROW(interp_gauss_precomp(xs, ys), std::domain_error); + + // xs must contain at least two elements + xs = {1}; + ys = {0, 2}; + EXPECT_THROW(interp_gauss_precomp(xs, ys), std::domain_error); + + // check that error throws when trying to interpolate out of range or nan + xs = {0, 1}; + ys = {0, 2}; + std::vector params = interp_gauss_precomp(xs, ys); + x = 1.1; + EXPECT_THROW(interp_gauss(xs, ys, params, x), std::domain_error); + x = -0.1; + EXPECT_THROW(interp_gauss(xs, ys, params, x), std::domain_error); + x = nan; + EXPECT_THROW(interp_gauss(xs, ys, params, x), std::domain_error); + + // ys can't contain nan + xs = {0, 1}; + ys = {0, nan}; + x = 0.5; + EXPECT_THROW(interp_gauss(xs, ys, params, x), std::domain_error); + + // xs can't contain nan + xs = {0, nan}; + ys = {0, 2}; + x = 0.5; + EXPECT_THROW(interp_gauss(xs, ys, params, x), std::domain_error); + + // xs must be increasing + xs = {1, 1}; + ys = {0, 2}; + EXPECT_THROW(interp_gauss(xs, ys, params, x), std::domain_error); + + // xs must contain at least two elements + xs = {1}; + ys = {0, 2}; + EXPECT_THROW(interp_gauss(xs, ys, params, x), std::domain_error); +} + +TEST(mathPrimInterpGauss, interp_line) { + using stan::math::interp_gauss; + using stan::math::interp_gauss_precomp; + + // check that interpolation of line returns the same function + // generate function tabulation + int n = 2; + double xmin = 0; + double xmax = 1; + std::vector xs = {0, 1}; + std::vector ys = {0, 2}; + + // vector of pts at which to compute interpolation + std::vector xs_new; + int n_interp = 100; + double t0 = xmin; + double t1 = xmax; + double t; + for (int i = 0; i < n_interp; i++) { + t = t0 + i * (t1 - t0) / (n_interp - 1); + xs_new.push_back(t); + } + + // create interpolation using precomp + std::vector ys_new_gaus(n_interp); + std::vector params = interp_gauss_precomp(xs, ys); + for (int i = 0; i < n_interp; i++) { + ys_new_gaus[i] = interp_gauss(xs, ys, params, xs_new[i]); + } + + // create interpolation without precomp + std::vector ys_new_gaus2 = interp_gauss(xs, ys, xs_new); + + // test points + double tmp, y; + for (int i = 0; i < n_interp; i++) { + tmp = (ys[1] - ys[0]) / (xs[1] - xs[0]); + y = tmp * xs_new[i] + ys[0] - tmp * xs[0]; + ASSERT_NEAR(ys_new_gaus[i], y, ABS_TOL); + ASSERT_NEAR(ys_new_gaus2[i], y, ABS_TOL); + } +} + +TEST(mathPrimInterpGauss, matching_reference_interp_pts) { + using stan::math::interp_gauss; + + // check that interpolation returns the same function + // when interpolation points are the same as reference points + + // generate function tabulation + int n = 3; + std::vector xs = {0, 1, 2}; + std::vector ys = {0, 2, 1}; + + // create interpolation points + std::vector xs_new = xs; + + // create interpolation + std::vector ys_new = interp_gauss(xs, ys, xs_new); + + // test points + for (int i = 0; i < n; i++) { + ASSERT_NEAR(ys_new[i], ys[i], 1e-8); + } +} + +TEST(mathPrimInterpGauss, interp_gauss_and_lin) { + using stan::math::interp_gauss; + using stan::math::interp_gauss_precomp; + using stan::math::interp_lin; + + // check that interpolation of line returns the same function + // generate function tabulation + int n = 30; + double xmin = 0; + double xmax = 1; + double x; + std::vector xs, ys; + for (int i = 0; i < n; i++) { + x = xmin + i * (xmax - xmin) / (n - 1); + xs.push_back(x); + ys.push_back(x * x); + } + + // vector of pts at which to compute interpolation + std::vector xs_new; + int n_interp = 100; + double t0 = xmin; + double t1 = xmax; + double t; + for (int i = 0; i < n_interp; i++) { + t = t0 + i * (t1 - t0) / (n_interp - 1); + xs_new.push_back(t); + } + + // create interpolation + std::vector ys_new_gaus(n_interp); + std::vector ys_new_lin(n_interp); + + // linear interpolation + ys_new_lin.resize(n_interp); + for (int i = 0; i < n_interp; i++) { + ys_new_lin[i] = interp_lin(xs, ys, xs_new[i]); + } + + // gaus interpolation + std::vector params = interp_gauss_precomp(xs, ys); + for (int i = 0; i < n_interp; i++) { + ys_new_gaus[i] = interp_gauss(xs, ys, params, xs_new[i]); + } + + // test points + double tmp, y; + for (int i = 0; i < n_interp; i++) { + ASSERT_NEAR(ys_new_lin[i], ys_new_gaus[i], 1e-4); + } +} diff --git a/test/unit/math/prim/fun/interp_lin_test.cpp b/test/unit/math/prim/fun/interp_lin_test.cpp new file mode 100644 index 00000000000..98f6a2fd240 --- /dev/null +++ b/test/unit/math/prim/fun/interp_lin_test.cpp @@ -0,0 +1,110 @@ +#include +#include +#include + +double ABS_TOL = 1e-12; + +TEST(mathPrimInterpLin, throwing) { + using stan::math::interp_lin; + double nan = std::numeric_limits::quiet_NaN(); + double x = 0.5; + std::vector xs, ys; + + // check that when xs are not increasing, an error throws + int n = 2; + xs = {1, 1}; + ys = {0, 2}; + EXPECT_THROW(interp_lin(xs, ys, x), std::domain_error); + + // check when xs contain a nan + xs = {nan, 1}; + ys = {0, 2}; + EXPECT_THROW(interp_lin(xs, ys, x), std::domain_error); + + // xs must contain at least two elements + xs = {1}; + ys = {0, 2}; + EXPECT_THROW(interp_lin(xs, ys, x), std::domain_error); + + // ys can't contain nan + xs = {0, 1}; + ys = {0, nan}; + x = 0.5; + EXPECT_THROW(interp_lin(xs, ys, x), std::domain_error); + + // x can't be nan + xs = {0, 1}; + ys = {0, 2}; + x = nan; + EXPECT_THROW(interp_lin(xs, ys, x), std::domain_error); +} + +TEST(mathPrimInterpLin, interp_line) { + using stan::math::interp_lin; + + // check that interpolation of line returns the same function + // generate function tabulation + int n = 2; + double xmin = 0; + double xmax = 1; + std::vector xs = {0, 1}; + std::vector ys = {0, 2}; + + // vector of pts at which to compute interpolation + std::vector xs_new; + int n_interp = 100; + double t0 = xmin; + double t1 = xmax; + double t; + for (int i = 0; i < n_interp; i++) { + t = t0 + i * (t1 - t0) / (n_interp - 1); + xs_new.push_back(t); + } + + // interpolate + std::vector ys_new(n_interp); + for (int i = 0; i < n_interp; i++) { + ys_new[i] = interp_lin(xs, ys, xs_new[i]); + } + + // test points + double tmp, y; + for (int i = 0; i < n_interp; i++) { + tmp = (ys[1] - ys[0]) / (xs[1] - xs[0]); + y = tmp * xs_new[i] + ys[0] - tmp * xs[0]; + ASSERT_NEAR(ys_new[i], y, ABS_TOL); + } + + // check values outside of range of reference points + ASSERT_NEAR(interp_lin(xs, ys, -1), ys[0], ABS_TOL); + ASSERT_NEAR(interp_lin(xs, ys, 100), ys[1], ABS_TOL); + + // xs with more than 2 elements + std::vector xs2 = {0, 1, 2, 3, 4, 5, 6}; + std::vector ys2 = {0, 2, 5, 2, 3, 2, 2}; + double x = 0.5; + ASSERT_NEAR(interp_lin(xs, ys, x), interp_lin(xs2, ys2, x), ABS_TOL); +} + +TEST(mathPrimInterpLin, matching_reference_interp_pts) { + using stan::math::interp_lin; + + // check that interpolation returns the same function + // when interpolation points are the same as reference points + + // generate function tabulation + int n = 3; + std::vector xs = {0, 1, 2}; + std::vector ys = {0, 2, 1}; + + // create interpolation points, same as reference points + std::vector xs_new = xs; + + // create interpolation + std::vector ys_new = interp_lin(xs, ys, xs_new); + + // test points + for (int i = 0; i < n; i++) { + ASSERT_NEAR(ys_new[i], ys[i], ABS_TOL); + } +}