-
-
Notifications
You must be signed in to change notification settings - Fork 367
/
laplace_sample.hpp
183 lines (168 loc) · 6.46 KB
/
laplace_sample.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
#ifndef STAN_SERVICES_OPTIMIZE_LAPLACE_SAMPLE_HPP
#define STAN_SERVICES_OPTIMIZE_LAPLACE_SAMPLE_HPP
#include <stan/callbacks/interrupt.hpp>
#include <stan/callbacks/logger.hpp>
#include <stan/callbacks/writer.hpp>
#include <stan/math/rev.hpp>
#include <stan/services/error_codes.hpp>
#include <stan/services/util/create_rng.hpp>
#include <string>
#include <type_traits>
#include <vector>
namespace stan {
namespace services {
namespace internal {
template <bool jacobian, typename Model>
void laplace_sample(const Model& model, const Eigen::VectorXd& theta_hat,
int draws, unsigned int random_seed, int refresh,
callbacks::interrupt& interrupt, callbacks::logger& logger,
callbacks::writer& sample_writer) {
if (draws <= 0) {
throw std::domain_error("Number of draws must be > 0; found draws = "
+ std::to_string(draws));
}
std::vector<std::string> unc_param_names;
model.unconstrained_param_names(unc_param_names, false, false);
int num_unc_params = unc_param_names.size();
if (theta_hat.size() != num_unc_params) {
throw ::std::domain_error(
"Specified mode is wrong size; expected "
+ std::to_string(num_unc_params)
+ " unconstrained parameters, but specified mode has size = "
+ std::to_string(theta_hat.size()));
}
std::vector<std::string> param_tp_gq_names;
model.constrained_param_names(param_tp_gq_names, true, true);
size_t draw_size = param_tp_gq_names.size();
// write names of params, tps, and gqs to sample writer
std::vector<std::string> names;
static const bool include_tp = true;
static const bool include_gq = true;
model.constrained_param_names(names, include_tp, include_gq);
names.push_back("log_p");
names.push_back("log_q");
sample_writer(names);
// create log density functor for vars and vals
std::stringstream log_density_msgs;
auto log_density_fun
= [&](const Eigen::Matrix<stan::math::var, -1, 1>& theta) {
return model.template log_prob<true, jacobian, stan::math::var>(
const_cast<Eigen::Matrix<stan::math::var, -1, 1>&>(theta),
&log_density_msgs);
};
// calculate inverse negative Hessian's Cholesky factor
if (refresh > 0) {
logger.info("Calculating Hessian\n");
}
double log_p; // dummy
Eigen::VectorXd grad; // dummy
Eigen::MatrixXd hessian;
interrupt();
math::internal::finite_diff_hessian_auto(log_density_fun, theta_hat, log_p,
grad, hessian);
if (refresh > 0) {
logger.info(log_density_msgs);
logger.info("\n");
}
// calculate Cholesky factor and inverse
interrupt();
if (refresh > 0) {
logger.info("Calculating inverse of Cholesky factor");
logger.info("\n");
}
Eigen::MatrixXd L_neg_hessian = (-hessian).llt().matrixL();
interrupt();
Eigen::MatrixXd inv_sqrt_neg_hessian = L_neg_hessian.inverse().transpose();
interrupt();
Eigen::MatrixXd half_hessian = 0.5 * hessian;
// generate draws and output to sample writer
if (refresh > 0) {
logger.info("Generating draws");
logger.info("\n");
}
boost::ecuyer1988 rng = util::create_rng(random_seed, 0);
Eigen::VectorXd draw_vec; // declare draw_vec, msgs here to avoid re-alloc
for (int m = 0; m < draws; ++m) {
interrupt(); // allow interpution each iteration
if (refresh > 0 && m % refresh == 0) {
logger.info("iteration: ");
logger.info(std::to_string(m));
logger.info("\n");
}
Eigen::VectorXd z(num_unc_params);
for (int n = 0; n < num_unc_params; ++n) {
z(n) = math::std_normal_rng(rng);
}
Eigen::VectorXd unc_draw = theta_hat + inv_sqrt_neg_hessian * z;
double log_p = log_density_fun(unc_draw).val();
Eigen::VectorXd diff = unc_draw - theta_hat;
double log_q = diff.transpose() * half_hessian * diff;
std::stringstream msgs;
model.write_array(rng, unc_draw, draw_vec, include_tp, include_gq, &msgs);
if (refresh > 0) {
logger.info(msgs);
logger.info("\n");
}
std::vector<double> draw(&draw_vec(0), &draw_vec(0) + draw_size);
draw.push_back(log_p);
draw.push_back(log_q);
sample_writer(draw);
}
}
} // namespace internal
/**
* Take the specified number of draws from the Laplace approximation
* for the model at the specified unconstrained mode, writing the
* draws, unnormalized log density, and unnormalized density of the
* approximation to the sample writer and writing messages to the
* logger, returning a return code of zero if successful.
*
* Interrupts are called between compute-intensive operations. To
* turn off all console messages sent to the logger, set refresh to 0.
* If an exception is thrown by the model, the return value is
* non-zero, and if refresh > 0, its message is given to the logger as
* an error.
*
* @tparam jacobian `true` to include Jacobian adjustment for
* constrained parameters
* @tparam Model a Stan model
* @parma[in] m model from which to sample
* @parma[in] theta_hat unconstrained mode at which to center the
* Laplace approximation
* @param[in] draws number of draws to generate
* @param[in] random_seed seed for generating random numbers in the
* Stan program and in sampling
* @param[in] refresh period between iterations at which updates are
* given, with a value of 0 turning off all messages
* @param[in] interrupt callback for interrupting sampling
* @param[in,out] logger callback for writing console messages from
* sampler and from Stan programs
* @param[in,out] sample_writer callback for writing parameter names
* and then draws
* @return a return code, with 0 indicating success
*/
template <bool jacobian, typename Model>
int laplace_sample(const Model& model, const Eigen::VectorXd& theta_hat,
int draws, unsigned int random_seed, int refresh,
callbacks::interrupt& interrupt, callbacks::logger& logger,
callbacks::writer& sample_writer) {
try {
internal::laplace_sample<jacobian>(model, theta_hat, draws, random_seed,
refresh, interrupt, logger,
sample_writer);
return error_codes::OK;
} catch (const std::exception& e) {
if (refresh >= 0) {
logger.error(e.what());
logger.error("\n");
}
} catch (...) {
if (refresh >= 0) {
logger.error("unknown exception during execution\n");
}
}
return error_codes::DATAERR;
}
} // namespace services
} // namespace stan
#endif