-
-
Notifications
You must be signed in to change notification settings - Fork 188
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Interpolation functions #1814
Interpolation functions #1814
Changes from 66 commits
f340d6f
63172f1
389f42b
98fdf8b
efde025
2169b76
b36451b
6c8726d
ee28a65
7582c50
e91a3fc
eb0a0d9
fc4abb5
774dccb
c4582be
74b8419
0ce6663
c4dd75d
be423da
05737f4
149f0d7
8594a47
ee5b696
adbfc60
e4589c0
3fde18a
c624bd0
77c0d25
f4dde7b
d37c844
d052649
dea4306
2a3020b
cdf5bbb
cce952b
e9e9786
2055a03
9ee99cc
4544f5e
5b9d65e
06d55bd
a5d00b6
b401faf
15e820f
2401a63
26b4529
561c909
2b98654
0cc8ae9
a3f988b
85da4f6
e1e423e
490cc03
22280c9
71d97f3
f5bbc96
0e32b0c
dc49bc1
c1cc956
09829be
c25fc04
91199f8
07060e7
b65bb3a
443d6ff
57c7b0d
fa5067f
68654cd
64853bc
62ea083
b5f69eb
d0d75a2
a992cad
226ee16
57c2f8b
2cbbad1
a9f71a2
7c8ab8b
c19300c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
#ifndef STAN_MATH_PRIM_FUN_CONV_GAUS_LINE | ||
#define STAN_MATH_PRIM_FUN_CONV_GAUS_LINE | ||
|
||
#include <stan/math/prim/meta.hpp> | ||
#include <stan/math/prim/prob/normal_cdf.hpp> | ||
#include <cmath> | ||
#include <vector> | ||
|
||
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$ | ||
* | ||
* @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 <typename Tx> | ||
inline return_type_t<Tx> 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); | ||
const double pi = stan::math::pi(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can directly call |
||
|
||
return_type_t<Tx> y | ||
= (a * x + b) * (normal_cdf(t1, x, sig) - normal_cdf(t0, x, sig)); | ||
y += -a * sig2 / sqrt(2 * pi * sig2) | ||
* (exp(-pow(t1 - x, 2) / (2 * sig2)) | ||
- exp(-pow(t0 - x, 2) / (2 * sig2))); | ||
|
||
return y; | ||
} | ||
|
||
} // namespace math | ||
} // namespace stan | ||
|
||
#endif |
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,196 @@ | ||||||||||||
#ifndef STAN_MATH_PRIM_FUN_GAUS_INTERP | ||||||||||||
#define STAN_MATH_PRIM_FUN_GAUS_INTERP | ||||||||||||
|
||||||||||||
#include <stan/math/prim/fun/conv_gaus_line.hpp> | ||||||||||||
#include <stan/math/prim/fun/square.hpp> | ||||||||||||
#include <stan/math/prim/err.hpp> | ||||||||||||
#include <cmath> | ||||||||||||
#include <vector> | ||||||||||||
#include <algorithm> | ||||||||||||
|
||||||||||||
namespace stan { | ||||||||||||
namespace math { | ||||||||||||
|
||||||||||||
namespace internal { | ||||||||||||
/* | ||||||||||||
* find the smallest difference between successive elements in a sorted vector | ||||||||||||
*/ | ||||||||||||
template <typename Tx> | ||||||||||||
inline double min_diff(int n, const std::vector<Tx>& xs) { | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Document |
||||||||||||
double dmin = value_of(xs[1]) - value_of(xs[0]); | ||||||||||||
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 struct created by the function gaus_interp_precomp. The algorithm | ||||||||||||
* used to create the mollifier is an iterative algorithm that works | ||||||||||||
* as follows. First a linear | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Docs need updated |
||||||||||||
* 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. | ||||||||||||
* | ||||||||||||
* @param xs vector of independent variable of reference points | ||||||||||||
* @param ys vector of dependent variable of reference points | ||||||||||||
* @param params a vector created by gaus_interp_precomp | ||||||||||||
* @param x the point at which to evaluate the interpolation | ||||||||||||
* @return value of the interpolation at x | ||||||||||||
*/ | ||||||||||||
template <typename Tx> | ||||||||||||
inline return_type_t<Tx> gaus_interp(const std::vector<double>& xs, | ||||||||||||
const std::vector<double>& ys, | ||||||||||||
const std::vector<double>& params, | ||||||||||||
const Tx& x) { | ||||||||||||
// enforce that interpolation point is between smallest and largest | ||||||||||||
// reference point | ||||||||||||
static char const* function = "gaus_interp"; | ||||||||||||
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(); | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [optional] we usually use small case |
||||||||||||
|
||||||||||||
// params is vector of the form (as, bs, sig2) | ||||||||||||
|
||||||||||||
// create copy of xs so that endpoints can be extended | ||||||||||||
std::vector<double> 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; | ||||||||||||
SteveBronder marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||
|
||||||||||||
// 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 = &(*lb) - &xs[0] - 1; | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I couldn't remember if we settled on std::distance or pointer arithmetic in our call. Will change. |
||||||||||||
ind_start = std::max(0, ind_start); | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Something is wrong if you are getting a negative number here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The problem is std::lower_bound finds the first element in a vector that is larger than an inputted value, x. But what we really want is the previous element, the last element that's less than x. So we need to subtract 1. However when the first element in the list is greater than x, that would give us -1, so we need to compensate for that. I couldn't figure out a cleaner way to do it, but let me know if you have a thought on how to do it. |
||||||||||||
|
||||||||||||
auto ub = std::upper_bound(xs.begin(), xs.end(), x + NSTDS * sig); | ||||||||||||
int ind_end = &(*ub) - &xs[0]; | ||||||||||||
ind_end = std::min(n - 1, ind_end); | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above |
||||||||||||
|
||||||||||||
// sum convolutions over intervals | ||||||||||||
return_type_t<Tx> 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]); | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this packing from stuffing the other parameters at the end? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of using the struct in the previous iteration of the code, I used a vector, |
||||||||||||
} | ||||||||||||
return y; | ||||||||||||
} | ||||||||||||
|
||||||||||||
/** | ||||||||||||
* This function was written to be used with gaus_interp. 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 struct containing slopes, intercepts, and width of kernel | ||||||||||||
*/ | ||||||||||||
inline std::vector<double> gaus_interp_precomp(const std::vector<double>& xs, | ||||||||||||
const std::vector<double>& ys) { | ||||||||||||
static char const* function = "gaus_interp_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; | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should these be things users can choose? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think so. This was discussed a bit in the design doc and I think it makes most sense at least for the time being, to not let users change this. |
||||||||||||
int n = xs.size(); | ||||||||||||
|
||||||||||||
// create the vector to be returned that consists of as, bs, sig2 | ||||||||||||
std::vector<double> params; | ||||||||||||
params.resize(2 * n - 1); | ||||||||||||
params[2 * n - 2] = square(min_diff(n, xs) * sig2_scale); | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Resizing just allocates memory but doesn't initialize so calling |
||||||||||||
|
||||||||||||
// copy ys into a new vector that will be changed | ||||||||||||
std::vector<double> y2s = ys; | ||||||||||||
|
||||||||||||
// interatively find interpolation that coincides with ys at xs | ||||||||||||
int max_iters = 50; | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this be user defined? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think so |
||||||||||||
double dd; | ||||||||||||
for (int j = 0; j < max_iters; j++) { | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [optional] both could be just |
||||||||||||
// 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]; | ||||||||||||
} | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You have to do the thing above with initialization or this is UB |
||||||||||||
|
||||||||||||
double dmax = 0; | ||||||||||||
for (size_t i = 0; i < n; i++) { | ||||||||||||
dd = ys[i] - gaus_interp(xs, y2s, params, xs[i]); | ||||||||||||
y2s[i] += dd; | ||||||||||||
dmax = std::max(std::abs(dd), dmax); | ||||||||||||
} | ||||||||||||
if (dmax < max_diff) | ||||||||||||
break; | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [optional] I prefer having scopes defined even for one liners
Suggested change
|
||||||||||||
} | ||||||||||||
return params; | ||||||||||||
SteveBronder marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||
} | ||||||||||||
|
||||||||||||
/** | ||||||||||||
* This function combines gaus_interp_precomp and gaus_interp. | ||||||||||||
* 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 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 <typename Tx> | ||||||||||||
inline std::vector<Tx> gaus_interp(const std::vector<double>& xs, | ||||||||||||
const std::vector<double>& ys, | ||||||||||||
const std::vector<Tx>& xs_new) { | ||||||||||||
int n_interp = xs_new.size(); | ||||||||||||
std::vector<Tx> ys_new(n_interp); | ||||||||||||
|
||||||||||||
// create interpolation | ||||||||||||
std::vector<double> params = gaus_interp_precomp(xs, ys); | ||||||||||||
for (int i = 0; i < n_interp; i++) { | ||||||||||||
ys_new[i] = gaus_interp(xs, ys, params, xs_new[i]); | ||||||||||||
} | ||||||||||||
return ys_new; | ||||||||||||
} | ||||||||||||
|
||||||||||||
} // namespace math | ||||||||||||
} // namespace stan | ||||||||||||
|
||||||||||||
#endif |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
#ifndef STAN_MATH_PRIM_FUN_LIN_INTERP | ||
#define STAN_MATH_PRIM_FUN_LIN_INTERP | ||
|
||
#include <stan/math/prim/err.hpp> | ||
#include <cmath> | ||
#include <vector> | ||
#include <algorithm> | ||
|
||
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 lin_interp(const std::vector<double>& xs, | ||
const std::vector<double>& ys, double x) { | ||
static char const* function = "lin_interp"; | ||
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 = &(*ub) - &(xs[0]); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same thing with distance |
||
|
||
// 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 <typename Tx> | ||
inline std::vector<Tx> lin_interp(const std::vector<double>& xs, | ||
const std::vector<double>& ys, | ||
const std::vector<Tx>& xs_new) { | ||
int n_interp = xs_new.size(); | ||
std::vector<Tx> ys_new(n_interp); | ||
|
||
// create interpolation | ||
for (int i = 0; i < n_interp; i++) { | ||
ys_new[i] = lin_interp(xs, ys, xs_new[i]); | ||
} | ||
return ys_new; | ||
} | ||
|
||
} // namespace math | ||
} // namespace stan | ||
|
||
#endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need
@tparam
for Tx