Skip to content

Commit 8911902

Browse files
authored
Merge pull request #2914 from stan-dev/feature/2913-finite-diff-Hvp
Add rev functor to compute hessian-times-vector by finite differences
2 parents 05f7fe6 + 5b751fb commit 8911902

3 files changed

Lines changed: 149 additions & 0 deletions

File tree

stan/math/rev/functor.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,6 @@
2929
#include <stan/math/rev/functor/partials_propagator.hpp>
3030
#include <stan/math/rev/functor/reduce_sum.hpp>
3131
#include <stan/math/rev/functor/finite_diff_hessian_auto.hpp>
32+
#include <stan/math/rev/functor/finite_diff_hessian_times_vector_auto.hpp>
3233

3334
#endif
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#ifndef STAN_MATH_REV_FUNCTOR_HESSIAN_TIMES_VECTOR_HPP
2+
#define STAN_MATH_REV_FUNCTOR_HESSIAN_TIMES_VECTOR_HPP
3+
4+
#include <stan/math/prim/fun/constants.hpp>
5+
#include <stan/math/rev/meta.hpp>
6+
#include <stan/math/rev/core.hpp>
7+
#include <stan/math/prim/fun/Eigen.hpp>
8+
#include <stan/math/rev/functor.hpp>
9+
#include <cmath>
10+
11+
namespace stan {
12+
namespace math {
13+
namespace internal {
14+
15+
/**
16+
* Calculate the value and the product of the Hessian and the specified
17+
* vector of the specified function at the specified argument using
18+
* central finite difference of gradients, automatically setting
19+
* the stepsize between the function evaluations along a dimension.
20+
*
21+
* <p>The functor must implement
22+
*
23+
* <code>
24+
* double operator()(const Eigen::Matrix<stan::math::var, -1, 1>&)
25+
* </code>
26+
*
27+
* <p>For details of the algorithm, see
28+
* https://justindomke.wordpress.com/2009/01/17/hessian-vector-products/
29+
*
30+
* <p>Step size is set automatically using
31+
* `sqrt(epsilon) * (1 + ||x||) / ||v||`,
32+
* as suggested in https://doi.org/10.1016/j.cam.2008.12.024
33+
*
34+
* 2 gradient calls are needed for the algorithm.
35+
*
36+
* @tparam F Type of function
37+
* @param[in] f Function
38+
* @param[in] x Argument to function
39+
* @param[in] v Vector to multiply Hessian with
40+
* @param[out] fx Function applied to argument
41+
* @param[out] hvp Product of Hessian and vector at argument
42+
*/
43+
template <typename F>
44+
void finite_diff_hessian_times_vector_auto(const F& f, const Eigen::VectorXd& x,
45+
const Eigen::VectorXd& v, double& fx,
46+
Eigen::VectorXd& hvp) {
47+
fx = f(x);
48+
49+
double epsilon = std::sqrt(EPSILON) * (1 + x.norm()) / v.norm();
50+
51+
Eigen::VectorXd v_eps = epsilon * v;
52+
53+
int d = x.size();
54+
double tmp;
55+
Eigen::VectorXd grad_forward(d);
56+
gradient(f, x + v_eps, tmp, grad_forward);
57+
58+
Eigen::VectorXd grad_backward(d);
59+
gradient(f, x - v_eps, tmp, grad_backward);
60+
61+
hvp.resize(d);
62+
hvp = (grad_forward - grad_backward) / (2 * epsilon);
63+
}
64+
} // namespace internal
65+
} // namespace math
66+
} // namespace stan
67+
#endif
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
#include <stan/math/rev.hpp>
2+
#include <gtest/gtest.h>
3+
#include <test/unit/math/rev/fun/util.hpp>
4+
#include <iostream>
5+
#include <stdexcept>
6+
#include <vector>
7+
8+
using Eigen::Dynamic;
9+
using Eigen::Matrix;
10+
11+
namespace finite_diff_hessian_times_vector_test {
12+
// fun1(x, y) = (x^2 * y) + (3 * y^2)
13+
struct fun1 {
14+
template <typename T>
15+
inline T operator()(const Matrix<T, Dynamic, 1>& x) const {
16+
return x(0) * x(0) * x(1) + 3.0 * x(1) * x(1);
17+
}
18+
};
19+
20+
struct fun2 {
21+
// fun2(x, y) = (x^2 * y) + (3 * y^2) + (5 * x * y) + sin(x)
22+
// d/dx fun2(x, y) = (2 * x * y) + (5 * y) + cos(x)
23+
// d/dy fun2(x, y) = (x^2) + (6 * y) + (5 * x)
24+
// d^2/dx^2 fun2(x, y) = (2 * y) - (sin(x))
25+
// d^2/dydx fun2(x, y) = (2 * x) + 5
26+
// d^2/dxdy fun2(x, y) = (2 * x) + 5
27+
// d^2/dy^2 fun2(x, y) = 6
28+
template <typename T>
29+
inline T operator()(const Matrix<T, Dynamic, 1>& x) const {
30+
using std::sin;
31+
return x(0) * x(0) * x(1) + 3.0 * x(1) * x(1) + 5.0 * x(0) * x(1)
32+
+ sin(x(0));
33+
}
34+
};
35+
36+
TEST(RevFunctor, finiteDiffHessianTimesVector) {
37+
using stan::math::internal::finite_diff_hessian_times_vector_auto;
38+
39+
fun1 f;
40+
41+
Matrix<double, Dynamic, 1> x(2);
42+
x << 2, -3;
43+
44+
Matrix<double, Dynamic, 1> v(2);
45+
v << 8, 5;
46+
47+
Matrix<double, Dynamic, 1> Hv;
48+
double fx;
49+
finite_diff_hessian_times_vector_auto(f, x, v, fx, Hv);
50+
51+
EXPECT_FLOAT_EQ(2 * 2 * -3 + 3.0 * -3 * -3, fx);
52+
53+
EXPECT_EQ(2, Hv.size());
54+
EXPECT_FLOAT_EQ(2 * x(1) * v(0) + 2 * x(0) * v(1), Hv(0));
55+
EXPECT_FLOAT_EQ(2 * x(0) * v(0) + 6 * v(1), Hv(1));
56+
}
57+
58+
TEST(RevFunctor, finiteDiffHessianTimesVector2) {
59+
using stan::math::internal::finite_diff_hessian_times_vector_auto;
60+
61+
fun2 f;
62+
63+
Matrix<double, Dynamic, 1> x(2);
64+
x << 13, -4;
65+
66+
Matrix<double, Dynamic, 1> v(2);
67+
v << 10, 0.2;
68+
69+
Matrix<double, Dynamic, 1> Hv;
70+
double fx;
71+
finite_diff_hessian_times_vector_auto(f, x, v, fx, Hv);
72+
73+
EXPECT_FLOAT_EQ(13 * 13 * -4 + 3 * -4 * -4 + 5 * 13 * -4 + std::sin(13), fx);
74+
75+
EXPECT_EQ(2, Hv.size());
76+
EXPECT_FLOAT_EQ((2 * x(1) - std::sin(x(0))) * v(0) + (2 * x(0) + 5) * v(1),
77+
Hv(0));
78+
EXPECT_FLOAT_EQ((2 * x(0) + 5) * v(0) + 6 * v(1), Hv(1));
79+
}
80+
81+
} // namespace finite_diff_hessian_times_vector_test

0 commit comments

Comments
 (0)