diff --git a/stan/math/mix.hpp b/stan/math/mix.hpp index 876916443ce..eddb96d780b 100644 --- a/stan/math/mix.hpp +++ b/stan/math/mix.hpp @@ -26,4 +26,9 @@ #include +#include +#include +#include +#include + #endif diff --git a/stan/math/mix/functor.hpp b/stan/math/mix/functor.hpp index 8e4367ee187..d8e76990be8 100644 --- a/stan/math/mix/functor.hpp +++ b/stan/math/mix/functor.hpp @@ -2,13 +2,16 @@ #define STAN_MATH_MIX_FUNCTOR_HPP #include -#include #include +#include #include #include #include #include +#include +#include +#include +#include #include #include - #endif diff --git a/stan/math/mix/functor/derivative.hpp b/stan/math/mix/functor/derivative.hpp index 478063fe82b..7acc00934da 100644 --- a/stan/math/mix/functor/derivative.hpp +++ b/stan/math/mix/functor/derivative.hpp @@ -1,9 +1,9 @@ #ifndef STAN_MATH_MIX_FUNCTOR_DERIVATIVE_HPP #define STAN_MATH_MIX_FUNCTOR_DERIVATIVE_HPP +#include #include #include -#include #include namespace stan { @@ -21,7 +21,7 @@ namespace math { * @param[out] dfx_dx Value of derivative */ template -void derivative(const F& f, const T& x, T& fx, T& dfx_dx) { +inline void derivative(const F& f, const T& x, T& fx, T& dfx_dx) { fvar x_fvar = fvar(x, 1.0); fvar fx_fvar = f(x_fvar); fx = fx_fvar.val_; diff --git a/stan/math/mix/functor/finite_diff_grad_hessian.hpp b/stan/math/mix/functor/finite_diff_grad_hessian.hpp index 95bab8427e3..42ac3f5652b 100644 --- a/stan/math/mix/functor/finite_diff_grad_hessian.hpp +++ b/stan/math/mix/functor/finite_diff_grad_hessian.hpp @@ -38,10 +38,10 @@ namespace math { * @param[in] epsilon perturbation size */ template -void finite_diff_grad_hessian(const F& f, const Eigen::VectorXd& x, double& fx, - Eigen::MatrixXd& hess, - std::vector& grad_hess_fx, - double epsilon = 1e-04) { +inline void finite_diff_grad_hessian(const F& f, const Eigen::VectorXd& x, + double& fx, Eigen::MatrixXd& hess, + std::vector& grad_hess_fx, + double epsilon = 1e-04) { int d = x.size(); grad_hess_fx.clear(); diff --git a/stan/math/mix/functor/finite_diff_grad_hessian_auto.hpp b/stan/math/mix/functor/finite_diff_grad_hessian_auto.hpp index 8c38ed5477f..b41a54e0320 100644 --- a/stan/math/mix/functor/finite_diff_grad_hessian_auto.hpp +++ b/stan/math/mix/functor/finite_diff_grad_hessian_auto.hpp @@ -41,9 +41,9 @@ namespace math { * @param[out] grad_hess_fx gradient of Hessian of function at argument */ template -void finite_diff_grad_hessian_auto(const F& f, const Eigen::VectorXd& x, - double& fx, Eigen::MatrixXd& hess, - std::vector& grad_hess_fx) { +inline void finite_diff_grad_hessian_auto( + const F& f, const Eigen::VectorXd& x, double& fx, Eigen::MatrixXd& hess, + std::vector& grad_hess_fx) { int d = x.size(); grad_hess_fx.clear(); diff --git a/stan/math/mix/functor/grad_hessian.hpp b/stan/math/mix/functor/grad_hessian.hpp index d8abb272feb..c3478bcc113 100644 --- a/stan/math/mix/functor/grad_hessian.hpp +++ b/stan/math/mix/functor/grad_hessian.hpp @@ -1,9 +1,9 @@ #ifndef STAN_MATH_MIX_FUNCTOR_GRAD_HESSIAN_HPP #define STAN_MATH_MIX_FUNCTOR_GRAD_HESSIAN_HPP +#include #include #include -#include #include #include @@ -39,7 +39,7 @@ namespace math { * @param[out] grad_H Gradient of the Hessian of function at argument */ template -void grad_hessian( +inline void grad_hessian( const F& f, const Eigen::Matrix& x, double& fx, Eigen::Matrix& H, std::vector >& diff --git a/stan/math/mix/functor/grad_tr_mat_times_hessian.hpp b/stan/math/mix/functor/grad_tr_mat_times_hessian.hpp index c8f72b98a00..7b7bec13b32 100644 --- a/stan/math/mix/functor/grad_tr_mat_times_hessian.hpp +++ b/stan/math/mix/functor/grad_tr_mat_times_hessian.hpp @@ -1,10 +1,10 @@ #ifndef STAN_MATH_MIX_FUNCTOR_GRAD_TR_MAT_TIMES_HESSIAN_HPP #define STAN_MATH_MIX_FUNCTOR_GRAD_TR_MAT_TIMES_HESSIAN_HPP +#include #include #include #include -#include #include #include @@ -12,7 +12,7 @@ namespace stan { namespace math { template -void grad_tr_mat_times_hessian( +inline void grad_tr_mat_times_hessian( const F& f, const Eigen::Matrix& x, const Eigen::Matrix& M, Eigen::Matrix& grad_tr_MH) { @@ -26,7 +26,7 @@ void grad_tr_mat_times_hessian( Matrix x_var(x.size()); for (int i = 0; i < x.size(); ++i) { - x_var(i) = x(i); + x_var.coeffRef(i) = x(i); } Matrix, Dynamic, 1> x_fvar(x.size()); diff --git a/stan/math/mix/functor/gradient_dot_vector.hpp b/stan/math/mix/functor/gradient_dot_vector.hpp index b664effd19b..5b5326ddb0a 100644 --- a/stan/math/mix/functor/gradient_dot_vector.hpp +++ b/stan/math/mix/functor/gradient_dot_vector.hpp @@ -1,19 +1,19 @@ #ifndef STAN_MATH_MIX_FUNCTOR_GRADIENT_DOT_VECTOR_HPP #define STAN_MATH_MIX_FUNCTOR_GRADIENT_DOT_VECTOR_HPP +#include #include #include -#include #include namespace stan { namespace math { template -void gradient_dot_vector(const F& f, - const Eigen::Matrix& x, - const Eigen::Matrix& v, T1& fx, - T1& grad_fx_dot_v) { +inline void gradient_dot_vector(const F& f, + const Eigen::Matrix& x, + const Eigen::Matrix& v, + T1& fx, T1& grad_fx_dot_v) { using Eigen::Matrix; Matrix, Eigen::Dynamic, 1> x_fvar(x.size()); for (int i = 0; i < x.size(); ++i) { diff --git a/stan/math/mix/functor/hessian.hpp b/stan/math/mix/functor/hessian.hpp index 601444384ea..ae0e93132d4 100644 --- a/stan/math/mix/functor/hessian.hpp +++ b/stan/math/mix/functor/hessian.hpp @@ -1,9 +1,9 @@ #ifndef STAN_MATH_MIX_FUNCTOR_HESSIAN_HPP #define STAN_MATH_MIX_FUNCTOR_HESSIAN_HPP +#include #include #include -#include #include namespace stan { @@ -39,9 +39,10 @@ namespace math { * @param[out] H Hessian of function at argument */ template -void hessian(const F& f, const Eigen::Matrix& x, - double& fx, Eigen::Matrix& grad, - Eigen::Matrix& H) { +inline void hessian(const F& f, + const Eigen::Matrix& x, + double& fx, Eigen::Matrix& grad, + Eigen::Matrix& H) { H.resize(x.size(), x.size()); grad.resize(x.size()); diff --git a/stan/math/mix/functor/hessian_block_diag.hpp b/stan/math/mix/functor/hessian_block_diag.hpp new file mode 100644 index 00000000000..cef06b5cf7e --- /dev/null +++ b/stan/math/mix/functor/hessian_block_diag.hpp @@ -0,0 +1,54 @@ +#ifndef STAN_MATH_MIX_FUNCTOR_HESSIAN_BLOCK_DIAG_HPP +#define STAN_MATH_MIX_FUNCTOR_HESSIAN_BLOCK_DIAG_HPP + +#include +#include + +namespace stan { +namespace math { + +/** + * Returns a block diagonal Hessian by computing the relevant directional + * derivatives and storing them in a matrix. + * For m the size of each block, the operations const m calls to + * hessian_times_vector, that is m forward sweeps and m reverse sweeps. + * @tparam F Type of function to differentiate. + * @tparam Eta Type of additional arguments passed to F. + * @tparam Args Type of variadic arguments passed to F. + * @param f Function to differentiate. + * @param x Arguments with respect to which we differentiate. + * @param eta Additional arguments for f. + * @param hessian_block_size + * @param args Additional variadic arguments for f. + */ +template * = nullptr> +inline Eigen::SparseMatrix hessian_block_diag( + F&& f, const Eigen::VectorXd& x, const Eta& eta, + const Eigen::Index hessian_block_size, Args&&... args) { + using Eigen::MatrixXd; + using Eigen::VectorXd; + + const Eigen::Index x_size = x.size(); + Eigen::SparseMatrix H(x_size, x_size); + H.reserve(Eigen::VectorXi::Constant(x_size, hessian_block_size)); + VectorXd v(x_size); + Eigen::Index n_blocks = x_size / hessian_block_size; + for (Eigen::Index i = 0; i < hessian_block_size; ++i) { + v.setZero(); + v(Eigen::seq(i, x_size - 1, hessian_block_size)).setOnes(); + VectorXd Hv = hessian_times_vector(f, x, eta, v, args...); + for (int j = 0; j < n_blocks; ++j) { + for (int k = 0; k < hessian_block_size; ++k) { + H.insert(k + j * hessian_block_size, i + j * hessian_block_size) + = Hv(k + j * hessian_block_size); + } + } + } + return H; +} + +} // namespace math +} // namespace stan + +#endif diff --git a/stan/math/mix/functor/hessian_times_vector.hpp b/stan/math/mix/functor/hessian_times_vector.hpp index 5d3bf9fb567..7849b4930f8 100644 --- a/stan/math/mix/functor/hessian_times_vector.hpp +++ b/stan/math/mix/functor/hessian_times_vector.hpp @@ -1,9 +1,9 @@ #ifndef STAN_MATH_MIX_FUNCTOR_HESSIAN_TIMES_VECTOR_HPP #define STAN_MATH_MIX_FUNCTOR_HESSIAN_TIMES_VECTOR_HPP +#include #include #include -#include #include #include @@ -11,11 +11,10 @@ namespace stan { namespace math { template -void hessian_times_vector(const F& f, - const Eigen::Matrix& x, - const Eigen::Matrix& v, - double& fx, - Eigen::Matrix& Hv) { +inline void hessian_times_vector( + const F& f, const Eigen::Matrix& x, + const Eigen::Matrix& v, double& fx, + Eigen::Matrix& Hv) { using Eigen::Matrix; // Run nested autodiff in this scope @@ -35,6 +34,7 @@ void hessian_times_vector(const F& f, Hv(i) = x_var(i).adj(); } } + template void hessian_times_vector(const F& f, const Eigen::Matrix& x, @@ -47,6 +47,31 @@ void hessian_times_vector(const F& f, Hv = H * v; } +/** + * Overload Hessian_times_vector function, under stan/math/mix/functor + * to handle functions which take in arguments eta, delta, delta_int, + * and pstream. + */ +template * = nullptr, + typename... Args> +inline Eigen::VectorXd hessian_times_vector(const F& f, + const Eigen::VectorXd& x, + const Eta& eta, + const Eigen::VectorXd& v, + Args&&... args) { + nested_rev_autodiff nested; + const Eigen::Index x_size = x.size(); + Eigen::Matrix x_var = x; + Eigen::Matrix, Eigen::Dynamic, 1> x_fvar(x_size); + for (Eigen::Index i = 0; i < x_size; i++) { + x_fvar(i) = fvar(x_var(i), v(i)); + } + fvar fx_fvar = f(x_fvar, eta, args...); + grad(fx_fvar.d_.vi_); + return x_var.adj(); +} + } // namespace math } // namespace stan + #endif diff --git a/stan/math/mix/functor/laplace_base_rng.hpp b/stan/math/mix/functor/laplace_base_rng.hpp new file mode 100644 index 00000000000..aea58f0cb18 --- /dev/null +++ b/stan/math/mix/functor/laplace_base_rng.hpp @@ -0,0 +1,91 @@ +#ifndef STAN_MATH_MIX_FUNCTOR_LAPLACE_BASE_RNG_HPP +#define STAN_MATH_MIX_FUNCTOR_LAPLACE_BASE_RNG_HPP + +#include +#include +#include + +#include + +namespace stan { +namespace math { + +/** + * In a latent gaussian model, + * + * theta ~ Normal(theta | 0, Sigma(phi, x)) + * y ~ pi(y | theta, eta) + * + * returns a multivariate normal random variate sampled + * from the Laplace approximation of p(theta_pred | y, phi, x_pred). + * Note that while the data is observed at x (train_tuple), the new samples + * are drawn for covariates x_pred (pred_tuple). + * To sample the "original" theta's, set pred_tuple = train_tuple. + * @tparam D Type of likelihood function. + * @tparam LLArgs Type of arguments of likelihood function. + * @tparam ThetaMatrix Type of latent Gaussian variables. + * @tparam EtaMatrix Type of additional arguments for likelihood function. + * @tparam CovarFun Type of covariance function. + * @tparam RNG Type of RNG number. + * @tparam TrainTuple Type of observed/training covariate. + * @tparam PredTuple Type of predictive covariate. + * @tparam CovarArgs Tuple of additional arguments for the covariance function + * @param ll_fun Likelihood function. + * @param ll_args Arguments for likelihood function. + * @param covariance_function Covariance function. + * @param eta Additional arguments for likelihood function. + * @param theta_0 Initial guess for finding the mode of the conditional + pi(theta_pred | y, phi, x_pred). + * @param options Control parameter for optimizer underlying Laplace approx. + * @param train_tuple Observed/training covariates for covariance function. + * @param pred_tuple Predictive covariates for covariance function. + * @param rng Rng number. + * @param msgs Stream for function prints. + * @param args Variadic arguments for likelihood function. + */ +template * = nullptr> +inline Eigen::VectorXd laplace_base_rng( + D&& ll_fun, LLArgs&& ll_args, CovarFun&& covariance_function, + const EtaMatrix& eta, const ThetaMatrix& theta_0, + const laplace_options& options, TrainTuple&& train_tuple, + PredTuple&& pred_tuple, RNG& rng, std::ostream* msgs, + CovarArgs&& covar_args) { + using Eigen::MatrixXd; + using Eigen::VectorXd; + auto covar_args_val = stan::math::to_ref(value_of(covar_args)); + auto eta_dbl = value_of(eta); + auto md_est = laplace_marginal_density_est( + ll_fun, ll_args, eta_dbl, value_of(theta_0), covariance_function, + std::tuple_cat(std::forward(train_tuple), covar_args_val), + options, msgs); + // Modified R&W method + MatrixXd covariance_pred = apply( + [&covariance_function, &msgs](auto&&... args_val) { + return covariance_function(args_val..., msgs); + }, + std::tuple_cat(std::forward(pred_tuple), covar_args_val)); + VectorXd pred_mean = covariance_pred * md_est.theta_grad; + if (options.solver == 1 || options.solver == 2) { + Eigen::MatrixXd V_dec = mdivide_left_tri( + md_est.L, md_est.W_r * covariance_pred); + Eigen::MatrixXd Sigma = covariance_pred - V_dec.transpose() * V_dec; + return multi_normal_rng(pred_mean, Sigma, rng); + } else { + Eigen::MatrixXd Sigma + = covariance_pred + - covariance_pred + * (md_est.W_r + - md_est.W_r + * md_est.LU.solve(md_est.covariance * md_est.W_r)) + * covariance_pred; + return multi_normal_rng(pred_mean, Sigma, rng); + } +} + +} // namespace math +} // namespace stan + +#endif diff --git a/stan/math/mix/functor/laplace_likelihood.hpp b/stan/math/mix/functor/laplace_likelihood.hpp new file mode 100644 index 00000000000..3f05bc98430 --- /dev/null +++ b/stan/math/mix/functor/laplace_likelihood.hpp @@ -0,0 +1,370 @@ +#ifndef STAN_MATH_MIX_FUNCTOR_LAPLACE_LIKELIHOOD_HPP +#define STAN_MATH_MIX_FUNCTOR_LAPLACE_LIKELIHOOD_HPP + +// #include +#include +#include +#include +#include + +namespace stan { +namespace math { +/** + * functions to compute the log density, first, second, + * and third-order derivatives for a likelihoood specified by the user. + */ +namespace laplace_likelihood { +namespace internal { +/** + * @tparam F Type of log likelihood function. + * @tparam Theta Type of latent Gaussian variable. + * @tparam Eta Type of parameter arguments for log likelihood function. + * @tparam Args Type of variadic arguments. + * @param f Log likelihood function. + * @param theta Latent Gaussian variable. + * @param eta Parameter arguments for likelihood function. + * @param args Additional variational arguments for likelihood function. + */ +template * = nullptr, + require_eigen_t* = nullptr> +inline auto log_likelihood(F&& f, Theta&& theta, Eta&& eta, Args&&... args) { + return std::forward(f)(std::forward(theta), std::forward(eta), + std::forward(args)...); +} + +/** + * @tparam F Type of log likelihood function. + * @tparam Theta Type of latent Gaussian variable. + * @tparam Eta Type of parameter arguments for log likelihood function. + * @tparam Args Type of variadic arguments. + * @param f Log likelihood function. + * @param theta Latent Gaussian model. + * @param eta Parameter argument for likelihood function. + * @param gradient Gradient of likelihood returned by function. + * @param hessian_block_size If the Hessian of the log likelihood function w.r.t + * the latent Gaussian variable is block-diagonal, + * size of each block. + * @param args Variadic arguments for the likelihood function. + */ +template * = nullptr, + require_eigen_t* = nullptr> +inline auto diff(F&& f, const Theta& theta, const Eta& eta, + const Eigen::Index hessian_block_size, Args&&... args) { + using Eigen::Dynamic; + using Eigen::Matrix; + const Eigen::Index theta_size = theta.size(); + const Eigen::Index eta_size = eta.size(); + Eigen::Matrix + theta_gradient; + Eigen::Matrix + eta_gradient; + { + nested_rev_autodiff nested; + Matrix theta_var = theta; + Matrix eta_var = eta; + + var f_var = f(theta_var, eta_var, args...); + grad(f_var.vi_); + theta_gradient = theta_var.adj(); + eta_gradient = eta_var.adj(); + } + + if (hessian_block_size == 1) { + Eigen::VectorXd v = Eigen::VectorXd::Ones(theta_size); + Eigen::VectorXd hessian_v = hessian_times_vector(f, theta, eta, v, args...); + Eigen::SparseMatrix hessian_theta(theta_size, theta_size); + hessian_theta.reserve(Eigen::VectorXi::Constant(theta_size, 1)); + for (Eigen::Index i = 0; i < theta_size; i++) { + hessian_theta.insert(i, i) = hessian_v(i); + } + return std::make_tuple(std::move(theta_gradient), std::move(eta_gradient), + (-hessian_theta).eval()); + } else { + return std::make_tuple( + std::move(theta_gradient), std::move(eta_gradient), + (-hessian_block_diag(f, theta, eta, hessian_block_size, args...)) + .eval()); + } +} + +/** + * @tparam F Type of log likelihood function. + * @tparam Theta Type of latent Gaussian variable. + * @tparam Eta Type of parameter arguments for likelihood function. + * @tparam Args Type of variadic arguments for likelihood function. + * @param f Log likelihood function. + * @param theta Latent Gaussian variable. + * @param eta Parameter arguments for likelihood function. + * @param args Variadic arguments for likelihood function. + */ +template * = nullptr, + require_eigen_t* = nullptr> +inline Eigen::VectorXd third_diff(F&& f, const Theta& theta, const Eta& eta, + Args&&... args) { + nested_rev_autodiff nested; + const Eigen::Index theta_size = theta.size(); + Eigen::Matrix theta_var = theta; + Eigen::Matrix>, Eigen::Dynamic, 1> theta_ffvar(theta_size); + for (Eigen::Index i = 0; i < theta_size; ++i) { + theta_ffvar(i) = fvar>(fvar(theta_var(i), 1.0), 1.0); + } + fvar> ftheta_ffvar = f(theta_ffvar, eta, args...); + grad(ftheta_ffvar.d_.d_.vi_); + return theta_var.adj(); +} + +/** + * @tparam F Type of log likelihood function. + * @tparam Theta Type of latent Gaussian variable. + * @tparam Eta Type of parameter arguments for likelihood function. + * @tparam Args Type of variadic arguments for likelihood function. + * @param f Log likelihood function. + * @param theta Latent Gaussian variable. + * @param eta Parameter arguments for + * @param A Matrix storing initial tangents for higher-order differentiation + * (line 21 in Algorithm 4, https://arxiv.org/pdf/2306.14976) + * @param hessian_block_size If the Hessian of the log likelihood w.r.t theta + * is block diagonal, size of each block. + * @param args Variational arguments for likelihood function. + */ +template * = nullptr, + require_eigen_t* = nullptr> +inline auto compute_s2(F&& f, const Theta& theta, const Eta& eta, + const Eigen::MatrixXd& A, const int hessian_block_size, + Args&&... args) { + using Eigen::Dynamic; + using Eigen::Matrix; + using Eigen::MatrixXd; + using Eigen::VectorXd; + + nested_rev_autodiff nested; + const Eigen::Index theta_size = theta.size(); + const Eigen::Index eta_size = eta.size(); + const Eigen::Index parm_size = theta_size + eta_size; + Matrix theta_var = theta; + Matrix eta_var = eta; + int n_blocks = theta_size / hessian_block_size; + fvar> target_ffvar = 0; + VectorXd v(theta_size); + VectorXd w(theta_size); + for (Eigen::Index i = 0; i < hessian_block_size; ++i) { + v.setZero(); + for (int j = i; j < theta_size; j += hessian_block_size) { + v(j) = 1; + } + Matrix, Dynamic, 1> theta_fvar(theta_size); + for (int j = 0; j < theta_size; ++j) { + theta_fvar(j) = fvar(theta_var(j), v(j)); + } + w.setZero(); + for (int j = 0; j < n_blocks; ++j) { + for (int k = 0; k < hessian_block_size; ++k) { + w(k + j * hessian_block_size) + = A(k + j * hessian_block_size, i + j * hessian_block_size); + } + } + Matrix>, Dynamic, 1> theta_ffvar(theta_size); + for (int j = 0; j < theta_size; ++j) { + theta_ffvar(j) = fvar>(theta_fvar(j), w(j)); + } + Matrix>, Eta::RowsAtCompileTime, Eta::ColsAtCompileTime> + eta_ffvar = eta_var.template cast>>(); + target_ffvar += f(theta_ffvar, eta_ffvar, args...); + } + grad(target_ffvar.d_.d_.vi_); + return std::make_pair((0.5 * theta_var.adj()).eval(), + (0.5 * eta_var.adj()).eval()); +} + +/** + * @tparam F Type of log likelihood function. + * @tparam Theta Type of latent Gaussian variable. + * @tparam Eta Type of parameter arguments for likelhood function. + * @tparam Args Type of variational arguments for likelhood function. + * @param f Log likelihood function. + * @param v Initial tangent. + * @param theta Latent Gaussian variable. + * @param eta Parameter arguments for likelhood function. + * @param args Variadic arguments for likelhood function. + */ +template * = nullptr, + require_eigen_t* = nullptr> +inline plain_type_t diff_eta_implicit(F&& f, const V_t& v, + const Theta& theta, const Eta& eta, + Args&&... args) { + using Eigen::Dynamic; + using Eigen::Matrix; + using Eigen::VectorXd; + if constexpr (Eta::RowsAtCompileTime == 0 && Eta::ColsAtCompileTime == 0) { + return plain_type_t{}; + } + nested_rev_autodiff nested; + const Eigen::Index eta_size = eta.size(); + Matrix eta_var = eta; + + // CHECK -- can we avoid declaring theta as fvar? + // We currently compute derivatives wrt eta, which is not needed. + const Eigen::Index theta_size = theta.size(); + Matrix theta_var = theta; + Matrix, Dynamic, 1> theta_fvar(theta_size); + for (Eigen::Index i = 0; i < theta_size; i++) { + theta_fvar(i) = fvar(theta_var(i), v(i)); + } + Matrix, Eta::RowsAtCompileTime, Eta::ColsAtCompileTime> eta_fvar + = eta_var.template cast>(); + + fvar f_fvar = f(theta_fvar, eta_fvar, args...); + grad(f_fvar.d_.vi_); + return eta_var.adj(); +} + +} // namespace internal + +/** + * @tparam F Type of log likelihood function. + * @tparam Theta Type of latent Gaussian variable. + * @tparam Eta Type of parameter argument for likelihood function. + * @tparam TupleArgs Type of arguments for covariance function. + * @param f Log likelihood function. + * @param theta Latent Gaussian model. + * @param eta Parameter arguments for likelihood function. + * @param ll_tup Arguments for covariance function. + * @param msgs stream messages. + */ +template * = nullptr, + require_eigen_t* = nullptr, + require_tuple_t* = nullptr> +inline auto log_likelihood(F&& f, const Theta& theta, const Eta& eta, + TupleArgs&& ll_tup, std::ostream* msgs) { + return apply( + [](auto&& f, auto&& theta, auto&& eta, auto&& msgs, auto&&... args) { + return internal::log_likelihood(f, theta, eta, args..., msgs); + }, + ll_tup, f, theta, eta, msgs); +} + +/** + * @tparam F Type of log likelihood function. + * @tparam Theta Type of latent Gaussian variable. + * @tparam Eta Type of parameter variable. + * @tparam TupleArgs Type of arguments for covariance function. + * @param f Log likelihood function. + * @param theta Latent Gaussian model. + * @param eta Parameter arguments for likelihood function. + * @param gradient Vector to store gradient of log likelihood w.r.t theta. + * @param hessian_block_size If Hessian of log likelihood w.r.t theta is + * block diagonal, size of block. + * @param ll_tuple Arguments of covariance function. + * @param msgs Stream messages. + */ +template * = nullptr, + require_eigen_t* = nullptr, + require_tuple_t* = nullptr> +inline auto diff(F&& f, const Theta& theta, const Eta& eta, + const Eigen::Index hessian_block_size, TupleArgs&& ll_tuple, + std::ostream* msgs) { + return apply( + [](auto&& f, auto&& theta, auto&& eta, auto hessian_block_size, + auto* msgs, auto&&... args) { + return internal::diff(f, theta, eta, hessian_block_size, args..., msgs); + }, + ll_tuple, f, theta, eta, hessian_block_size, msgs); +} + +/** + * @tparam F Type of log likelhood function. + * @tparam Theta Type of latent Gaussian variable. + * @tparam Eta Type of parameter arguments for likelhood function. + * @tparam TupleArgs Type of arguments for covariance function. + * @param f Log likelihood function. + * @param theta Latent Gaussian variable. + * @param eta Parameter argument for likelihood funciton. + * @param ll_args Variadic arguments for likelihood function. + * @param msgs Streaming message. + */ +template * = nullptr, + require_eigen_t* = nullptr, + require_tuple_t* = nullptr> +inline Eigen::VectorXd third_diff(F&& f, const Theta& theta, const Eta& eta, + TupleArgs&& ll_args, std::ostream* msgs) { + return apply( + [](auto&& f, auto&& theta, auto&& eta, auto&& msgs, auto&&... args) { + return internal::third_diff(f, theta, eta, args..., msgs); + }, + ll_args, f, theta, eta, msgs); +} + +/** + * @tparam F Type of log likelhood function. + * @tparam Theta Type of latent Gaussian ba + * @tparam Eta Type of parameter argument for likelihood function. + * @tparam TupleArgs Type of arguments for covariance function. + * @param f Log likelihood function. + * @param theta Latent Gaussian variable. + * @param eta Parameter arguments for likelihood function. + * @param A Matrix storing initial tangents for higher-order differentiation + * (line 21 in Algorithm 4, https://arxiv.org/pdf/2306.14976) + * @param hessian_block_size If Hessian of log likelihood w.r.t theta is + * block diagonal, size of block. + * @param ll_args Variadic arguments for likelihood function. + * @param msgs Streaming messages. + */ +template * = nullptr, + require_eigen_t* = nullptr, + require_tuple_t* = nullptr> +inline auto compute_s2(F&& f, const Theta& theta, const Eta& eta, + const Eigen::MatrixXd& A, int hessian_block_size, + TupleArgs&& ll_args, std::ostream* msgs) { + return apply( + [](auto&& f, auto&& theta, auto&& eta, auto&& A, auto hessian_block_size, + auto* msgs, auto&&... args) { + return internal::compute_s2(f, theta, eta, A, hessian_block_size, + args..., msgs); + }, + ll_args, f, theta, eta, A, hessian_block_size, msgs); +} + +/** + * @tparam F Type of log likelihood function. + * @tparam V_t Type of initial tangent. + * @tparam Theta Type of latent Gaussian variable. + * @tparam Eta Type of parameter arguments for likelihood function. + * @tparam TupleArgs Type of variadic arguments for likelihood function. + * @param f Log likelihood function. + * @param v Initial tangent. + * @param theta Latent Gaussian variable. + * @param eta Parameter argument for likelihood function. + * @param ll_args Variadic arguments for likelihood function. + * @param msgs Streaming messages. + */ +template * = nullptr, + require_eigen_vector_t* = nullptr, + require_eigen_t* = nullptr> +inline plain_type_t diff_eta_implicit(F&& f, const V_t& v, + const Theta& theta, const Eta& eta, + TupleArgs&& ll_args, + std::ostream* msgs) { + return apply( + [](auto&& f, auto&& v, auto&& theta, auto&& eta, auto&& msgs, + auto&&... args) { + return internal::diff_eta_implicit(f, v, theta, eta, args..., msgs); + }, + ll_args, f, v, theta, eta, msgs); +} + +} // namespace laplace_likelihood + +} // namespace math +} // namespace stan + +#endif diff --git a/stan/math/mix/functor/laplace_marginal_density.hpp b/stan/math/mix/functor/laplace_marginal_density.hpp new file mode 100644 index 00000000000..1e2af58d17a --- /dev/null +++ b/stan/math/mix/functor/laplace_marginal_density.hpp @@ -0,0 +1,805 @@ +#ifndef STAN_MATH_MIX_FUNCTOR_LAPLACE_MARGINAL_DENSITY_HPP +#define STAN_MATH_MIX_FUNCTOR_LAPLACE_MARGINAL_DENSITY_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +// Reference for calculations of marginal and its gradients: +// Margossian et al (2020), https://arxiv.org/abs/2004.12550 +// and Margossian (2022), https://doi.org/10.7916/0wsc-kz90 + +// TODO(Charles) -- either use Eigen's .solve() or mdivide_left_tri +// The code needs to be more consistent + +namespace stan { +namespace math { + +/** + * Options for the laplace sampler + */ +struct laplace_options { + /* Size of the blocks in block diagonal hessian*/ + int hessian_block_size; + /** + * Which Newton solver to use: + * (1) method using the root of W + * (2) method using the root of the covariance + * (3) method using an LU decomposition + */ + int solver; + /* Maximum number of steps in line search*/ + int max_steps_line_search; + /* iterations end when difference in objective function is less than tolerance + */ + double tolerance; + /* Maximum number of steps*/ + int64_t max_num_steps; +}; +template +struct laplace_density_estimates { + // log marginal density + double lmd{std::numeric_limits::infinity()}; + // Evaluated covariance function for the latent gaussian variable + Covar covariance; + // Mode + Theta theta; + // the square root of the negative Hessian or the negative Hessian, depending + // on which solver we use + WR W_r; + // cholesky decomposition of stabilized inverse covariance + L_t L; + // element in the Newton step + A_vec a; + // the gradient of the log density with respect to theta + ThetaGrad theta_grad; + // the gradient of the log density with respect to eta + EtaGrad eta_grad; + // LU matrix + LU_t LU; + // Cholesky of the covariance matrix + KRoot K_root; + laplace_density_estimates(double lmd_, Covar&& covariance_, Theta&& theta_, + WR&& W_r_, L_t&& L_, A_vec&& a_, + ThetaGrad&& theta_grad_, EtaGrad&& eta_grad_, + LU_t&& LU_, KRoot&& K_root_) + : lmd(lmd_), + covariance(std::move(covariance_)), + theta(std::move(theta_)), + W_r(std::move(W_r_)), + L(std::move(L_)), + a(std::move(a_)), + theta_grad(std::move(theta_grad_)), + eta_grad(std::move(eta_grad_)), + LU(std::move(LU_)), + K_root(std::move(K_root_)) {} +}; + +/** + * Function to compute the pseudo target, $\tilde Z$, + * with a custom derivative method + * NOTE: we actually don't need to compute the pseudo-target, only its + * derivative + * @tparam Kmat Type inheriting from `Eigen::EigenBase` with dynamic rows and + * columns + * @tparam AVec Type of matrix of initial tangents + * @tparam RMat Type of the stable R matrix + * @tparam LGradVec Type of the gradient of the log likelihood + * @tparam S2Vec Type of the s2 vector + */ +template < + typename KMat, typename AVec, typename RMat, typename LGradVec, + typename S2Vec, + require_eigen_matrix_dynamic_vt* = nullptr> +inline constexpr double laplace_pseudo_target(KMat&& /* K */, AVec&& /* a */, + RMat&& /* R */, + LGradVec&& /* l_grad */, + S2Vec&& /* s2 */) { + return 0; +} + +/** + * Overload function for case where K is passed as a matrix of var + * @tparam Kmat Type inheriting from `Eigen::EigenBase` with dynamic rows and + * columns + * @tparam AVec Type inheriting from `Eigen::EigenBase` with dynamic columns and + * a single row + * @tparam RMat Type inheriting from `Eigen::EigenBase` with dynamic rows and + * columns + * @tparam LGradVec Type inheriting from `Eigen::EigenBase` with dynamic rows + * and a single column + * @tparam S2Vec Type of s2 vector + * @param K Covariance matrix + * @param a Saved a vector from Newton solver + * @param R Stable R matrix + * @param l_grad Saved gradient of log likelihood + * @param s2 Gradient of log determinant w.r.t latent Gaussian variable + */ +template * = nullptr> +inline auto laplace_pseudo_target(KMat&& K, AVec&& a, RMat&& R, + LGradVec&& l_grad, S2Vec&& s2) { + const Eigen::Index dim_theta = K.rows(); + auto K_arena = to_arena(std::forward(K)); + auto&& a_ref = to_ref(std::forward(a)); + auto&& R_ref = to_ref(std::forward(R)); + auto&& s2_ref = to_ref(std::forward(s2)); + auto&& l_grad_ref = to_ref(std::forward(l_grad)); + arena_matrix K_adj_arena + = 0.5 * a_ref * a_ref.transpose() - 0.5 * R_ref + + s2_ref * l_grad_ref.transpose() + - (R_ref * (value_of(K_arena) * s2_ref)) * l_grad_ref.transpose(); + return make_callback_var(0.0, [K_arena, K_adj_arena](auto&& vi) mutable { + K_arena.adj().array() += vi.adj() * K_adj_arena.array(); + }); +} + +/** + * Return the matrix square-root for a block diagonal matrix + * @param W Block-diagonal matrix + * @param block_size Size of blocks in W + */ +inline Eigen::SparseMatrix block_matrix_sqrt( + const Eigen::SparseMatrix& W, const Eigen::Index block_size) { + int n_block = W.cols() / block_size; + Eigen::MatrixXd local_block(block_size, block_size); + Eigen::MatrixXd local_block_sqrt(block_size, block_size); + Eigen::SparseMatrix W_root(W.rows(), W.cols()); + W_root.reserve(Eigen::VectorXi::Constant(W_root.cols(), block_size)); + + // No block operation available for sparse matrices, so we have to loop + // See https://eigen.tuxfamily.org/dox/group__TutorialSparse.html#title7 + for (int i = 0; i < n_block; i++) { + for (int k = 0; k < block_size; k++) { + for (int j = 0; j < block_size; j++) { + local_block.coeffRef(j, k) + = W.coeff(i * block_size + j, i * block_size + k); + } + } + local_block_sqrt = local_block.sqrt(); + for (int k = 0; k < block_size; k++) { + for (int j = 0; j < block_size; j++) { + W_root.insert(i * block_size + j, i * block_size + k) + = local_block_sqrt(j, k); + } + } + } + W_root.makeCompressed(); + return W_root; +} + +/** + * For a latent Gaussian model with hyperparameters phi and eta, + * latent variables theta, and observations y, this function computes + * an approximation of the log marginal density, p(y | phi). + * This is done by marginalizing out theta, using a Laplace + * approxmation. The latter is obtained by finding the mode, + * via Newton's method, and computing the Hessian of the likelihood. + * + * The convergence criterion for the Newton is a small change in + * log marginal density. The user controls the tolerance (i.e. + * threshold under which change is deemed small enough) and + * maximum number of steps. + * TODO(Charles): add more robust convergence criterion. + * + * A description of this algorithm can be found in: + * - (2023) Margossian, "General Adjoint-Differentiated Laplace approximation", + * https://arxiv.org/pdf/2306.14976. + * Additional references include: + * - (2020) Margossian et al, "HMC using an adjoint-differentiated Laplace...", + * NeurIPS, https://arxiv.org/abs/2004.12550. + * - (2006) Rasmussen and Williams, "Gaussian Processes for Machine Learning", + * second edition, MIT Press, algorithm 3.1. + * + * Variables needed for the gradient or generating quantities + * are stored by reference. + * + * @tparam LLFun Type with a valid `operator(Theta, Eta, InnerLLTupleArgs)` + * where `InnerLLTupleArgs` are the elements of `LLTupleArgs` + * @tparam LLTupleArgs A tuple whose elements follow the types required for + * `LLFun` + * @tparam CovarFun Type with a valid `operator(InnerCovarArgs)` where + * `InnerCovarArgs` are a parameter pack of the element types of `CovarArgs` + * @tparam Theta Type derived from `Eigen::EigenBase` with dynamic rows and a + * single column + * @tparam Eta Type derived from `Eigen::EigenBase` with dynamic rows and a + * single column + * @tparam CovarArgs A tuple whose elements follow the types required for + * `CovarFun` + * @param[in] ll_fun A log likelihood functor + * @param[in] ll_args Tuple containing parameters for `LLFun` + * @param[in] covariance_function Functor for the covariance function + * @param[in] eta hyperparameter (input for likelihood) + * @param[in] theta_0 the initial guess for the Laplace optimization + * @param[in,out] msgs stream for messages from likelihood and covariance + * @param[in] options A set of options for tuning the solver + * @param[in] covar_args Tuple of arguments to pass to the covariance matrix + * functor + * + * @return A struct containing + * 1. lmd the log marginal density, p(y | phi) + * 2. covariance the evaluated covariance function for the latent gaussian + * variable + * 3. theta a vector to store the mode + * 4. W_r a vector to store the square root of the + * negative Hessian or the negative Hessian, depending + * on which solver we use + * 5. L cholesky decomposition of stabilized inverse covariance + * 6. a element in the Newton step + * 7. l_grad the log density of the likelihood, evaluated at the mode + * + */ +template >* = nullptr, + require_eigen_vector_t* = nullptr> +inline auto laplace_marginal_density_est(LLFun&& ll_fun, LLTupleArgs&& ll_args, + Eta&& eta, Theta&& theta_0, + CovarFun&& covariance_function, + CovarArgs&& covar_args, + const laplace_options& options, + std::ostream* msgs) { + using Eigen::MatrixXd; + using Eigen::SparseMatrix; + using Eigen::VectorXd; + + check_nonzero_size("laplace_marginal", "initial guess", theta_0); + check_finite("laplace_marginal", "initial guess", theta_0); + check_nonnegative("laplace_marginal", "tolerance", options.tolerance); + check_positive("laplace_marginal", "max_num_steps", options.max_num_steps); + check_positive("laplace_marginal", "hessian_block_size", + options.hessian_block_size); + check_nonnegative("laplace_marginal", "max_steps_line_search", + options.max_steps_line_search); + + Eigen::MatrixXd covariance = stan::math::apply( + [msgs, &covariance_function](auto&&... args) { + return covariance_function(args..., msgs); + }, + covar_args); + + auto throw_overstep = [](const auto max_num_steps) STAN_COLD_PATH { + throw std::domain_error( + std::string("laplace_marginal_density: max number of iterations: ") + + std::to_string(max_num_steps) + " exceeded."); + }; + auto line_search = [](auto& objective_new, auto& a, auto& theta, + const auto& a_old, const auto& covariance, + const auto& ll_fun, auto&& ll_args, const auto& eta, + const auto max_steps_line_search, + const auto objective_old, auto* msgs) mutable { + for (int j = 0; + j < max_steps_line_search && (objective_new < objective_old); ++j) { + a = (a + a_old) * 0.5; // TODO(Charles) -- generalize for any factor + theta = covariance * a; + if (Eigen::isfinite(theta.array()).sum()) { + objective_new = -0.5 * a.dot(theta) + + laplace_likelihood::log_likelihood(ll_fun, theta, eta, + ll_args, msgs); + } else { + break; + } + } + }; + promote_scalar_t theta_grad; + promote_scalar_t eta_grad; + const Eigen::Index theta_size = theta_0.size(); + std::decay_t theta = theta_0; + double objective_old = -1e+10; // CHECK -- what value to use? + double objective_new = -1e+10; + Eigen::VectorXd a_old; + if (options.solver == 1 && options.hessian_block_size == 1) { + SparseMatrix W; + for (Eigen::Index i = 0; i <= options.max_num_steps; i++) { + std::tie(theta_grad, eta_grad, W) = laplace_likelihood::diff( + ll_fun, theta, eta, options.hessian_block_size, ll_args, msgs); + + // Compute matrix square-root of W. If all elements of W are positive, + // do an element wise square-root. Else try a matrix square-root + bool W_is_spd = true; + for (Eigen::Index i = 0; i < theta_0.size(); i++) { + W_is_spd &= (W.coeff(i, i) < 0); + } + Eigen::SparseMatrix W_r; + if (W_is_spd) { + W_r = W.cwiseSqrt(); + } else { + W_r = block_matrix_sqrt(W, options.hessian_block_size); + } + auto B = MatrixXd::Identity(theta_size, theta_size) + + W_r.diagonal().asDiagonal() * covariance + * W_r.diagonal().asDiagonal(); + Eigen::MatrixXd L = std::move(B) + .template selfadjointView() + .llt() + .matrixL(); + const double B_log_determinant = 2.0 * L.diagonal().array().log().sum(); + VectorXd b = W.diagonal().cwiseProduct(theta) + theta_grad; + Eigen::VectorXd a + = b + - W_r + * mdivide_left_tri( + L.transpose(), + mdivide_left_tri( + L, W_r.diagonal().cwiseProduct(covariance * b))); + + // Simple Newton step + theta = covariance * a; + objective_old = objective_new; + if (!(Eigen::isinf(theta.array()).any())) { + objective_new = -0.5 * a.dot(theta) + + laplace_likelihood::log_likelihood(ll_fun, theta, eta, + ll_args, msgs); + } + if (options.max_steps_line_search && i != 0) { + line_search(objective_new, a, theta, a_old, covariance, ll_fun, ll_args, + eta, options.max_steps_line_search, objective_old, msgs); + } + a_old = a; + // Check for convergence + if (abs(objective_new - objective_old) < options.tolerance) { + return laplace_density_estimates{ + objective_new - 0.5 * B_log_determinant, + std::move(covariance), + std::move(theta), + std::move(W_r), + std::move(L), + std::move(a), + std::move(theta_grad), + std::move(eta_grad), + Eigen::PartialPivLU{}, + Eigen::MatrixXd(0, 0)}; + } + } + throw_overstep(options.max_num_steps); + } else if (options.solver == 1 && !(options.hessian_block_size == 1)) { + SparseMatrix W; + for (Eigen::Index i = 0; i <= options.max_num_steps; i++) { + std::tie(theta_grad, eta_grad, W) = laplace_likelihood::diff( + ll_fun, theta, eta, options.hessian_block_size, ll_args, msgs); + Eigen::SparseMatrix W_r + = block_matrix_sqrt(W, options.hessian_block_size); + auto B = MatrixXd::Identity(theta_size, theta_size) + + W_r * (covariance * W_r); + Eigen::MatrixXd L = std::move(B) + .template selfadjointView() + .llt() + .matrixL(); + const double B_log_determinant = 2.0 * L.diagonal().array().log().sum(); + VectorXd b = W * theta + theta_grad; + Eigen::VectorXd a + = b + - W_r + * mdivide_left_tri( + transpose(L), mdivide_left_tri( + L, W_r * (covariance * b))); + // Simple Newton step + theta = covariance * a; + objective_old = objective_new; + if (std::isfinite(theta.sum())) { + objective_new = -0.5 * a.dot(theta) + + laplace_likelihood::log_likelihood(ll_fun, theta, eta, + ll_args, msgs); + } + if (options.max_steps_line_search > 0 && i != 0) { + line_search(objective_new, a, theta, a_old, covariance, ll_fun, ll_args, + eta, options.max_steps_line_search, objective_old, msgs); + } + a_old = a; + // Check for convergence + if (abs(objective_new - objective_old) < options.tolerance) { + return laplace_density_estimates{ + objective_new - 0.5 * B_log_determinant, + std::move(covariance), + std::move(theta), + std::move(W_r), + std::move(L), + std::move(a), + std::move(theta_grad), + std::move(eta_grad), + Eigen::PartialPivLU{}, + Eigen::MatrixXd(0, 0)}; + } + } + throw_overstep(options.max_num_steps); + } else if (options.solver == 2) { + SparseMatrix W; + for (Eigen::Index i = 0; i <= options.max_num_steps; i++) { + std::tie(theta_grad, eta_grad, W) = laplace_likelihood::diff( + ll_fun, theta, eta, options.hessian_block_size, ll_args, msgs); + Eigen::MatrixXd K_root + = covariance.template selfadjointView().llt().matrixL(); + auto B = MatrixXd::Identity(theta_size, theta_size) + + K_root.transpose() * W * K_root; + Eigen::MatrixXd L = std::move(B) + .template selfadjointView() + .llt() + .matrixL(); + const double B_log_determinant = 2.0 * L.diagonal().array().log().sum(); + VectorXd b = W * theta + theta_grad; + Eigen::VectorXd a = mdivide_left_tri( + K_root.transpose(), + mdivide_left_tri( + L.transpose(), + mdivide_left_tri(L, K_root.transpose() * b))); + // Simple Newton step + theta = covariance * a; + objective_old = objective_new; + if (std::isfinite(theta.sum())) { + objective_new = -0.5 * a.dot(theta) + + laplace_likelihood::log_likelihood(ll_fun, theta, eta, + ll_args, msgs); + } + // linesearch + if (options.max_steps_line_search > 0 && i != 0) { + line_search(objective_new, a, theta, a_old, covariance, ll_fun, ll_args, + eta, options.max_steps_line_search, objective_old, msgs); + } + a_old = a; + // Check for convergence + if (abs(objective_new - objective_old) < options.tolerance) { + return laplace_density_estimates{ + objective_new - 0.5 * B_log_determinant, + std::move(covariance), + std::move(theta), + std::move(W), + std::move(L), + std::move(a), + std::move(theta_grad), + std::move(eta_grad), + Eigen::PartialPivLU{}, + std::move(K_root)}; + } + } + throw_overstep(options.max_num_steps); + } else if (options.solver == 3) { + SparseMatrix W; + for (Eigen::Index i = 0; i <= options.max_num_steps; i++) { + std::tie(theta_grad, eta_grad, W) = laplace_likelihood::diff( + ll_fun, theta, eta, options.hessian_block_size, ll_args, msgs); + auto B = MatrixXd::Identity(theta_size, theta_size) + covariance * W; + Eigen::PartialPivLU LU + = Eigen::PartialPivLU(std::move(B)); + // L on upper and U on lower triangular + auto&& U = LU.matrixLU(); + // Compute log-determinant (Charles: Verify this is correct) + double B_log_determinant = 0.0; + int signDet = LU.permutationP().determinant(); // +1 or -1 + for (Eigen::Index i = 0; i < U.rows(); ++i) { + B_log_determinant += std::log(std::abs(U.coeff(i, i))); + signDet *= (U.coeff(i, i) >= 0) ? 1 : -1; + } + B_log_determinant *= signDet; + // const double B_log_determinant = log(LU.determinant()); + VectorXd b = W * theta + theta_grad; + Eigen::VectorXd a = b - W * LU.solve(covariance * b); + // Simple Newton step + theta = covariance * a; + objective_old = objective_new; + + if (std::isfinite(theta.sum())) { + objective_new = -0.5 * a.dot(theta) + + laplace_likelihood::log_likelihood(ll_fun, theta, eta, + ll_args, msgs); + } + + // linesearch + // CHECK -- does linesearch work for options.solver 2? + if (options.max_steps_line_search > 0 && i != 0) { + line_search(objective_new, a, theta, a_old, covariance, ll_fun, ll_args, + eta, options.max_steps_line_search, objective_old, msgs); + } + a_old = a; + // Check for convergence + if (abs(objective_new - objective_old) < options.tolerance) { + return laplace_density_estimates{ + objective_new - 0.5 * B_log_determinant, + std::move(covariance), + std::move(theta), + std::move(W), + Eigen::MatrixXd(0, 0), + std::move(a), + std::move(theta_grad), + std::move(eta_grad), + std::move(LU), + Eigen::MatrixXd(0, 0)}; + } + } + throw_overstep(options.max_num_steps); + } + throw std::domain_error( + std::string("You chose a solver (") + std::to_string(options.solver) + + ") that is not valid. Please choose either 1, 2, or 3."); +} + +/** + * For a latent Gaussian model with global parameters phi, latent + * variables theta, and observations y, this function computes + * an approximation of the log marginal density, p(y | phi). + * This is done by marginalizing out theta, using a Laplace + * approxmation. The latter is obtained by finding the mode, + * using a custom Newton method, and the Hessian of the likelihood. + * + * The convergence criterion for the Newton is a small change in + * log marginal density. The user controls the tolerance (i.e. + * threshold under which change is deemed small enough) and + * maximum number of steps. + * + * Wrapper for when the hyperparameters are passed as a double. + * + * @tparam LLFun Type with a valid `operator(Theta, Eta, InnerLLTupleArgs)` + * where `InnerLLTupleArgs` are the elements of `LLTupleArgs` + * @tparam LLTupleArgs A tuple whose elements follow the types required for + * `LLFun` + * @tparam CovarFun Type with a valid `operator(InnerCovarArgs)` where + * `InnerCovarArgs` are a parameter pack of the element types of `CovarArgs` + * @tparam Theta Type derived from `Eigen::EigenBase` with dynamic rows and a + * single column + * @tparam Eta Type derived from `Eigen::EigenBase` with dynamic rows and a + * single column + * @tparam CovarArgs A tuple whose elements follow the types required for + * `CovarFun` + * @param[in] ll_fun A log likelihood functor + * @param[in] ll_args Tuple containing parameters for `LLFun` + * @param[in] covariance_function Functor for the covariance function + * @param[in] eta hyperparameter (input for likelihood) + * @param[in] theta_0 the initial guess for the Laplace optimization + * @param[in,out] msgs stream for messages from likelihood and covariance + * @param[in] options A set of options for tuning the solver + * @param[in] covar_args Tuple of arguments to pass to the covariance matrix + * functor + * @return the log maginal density, p(y | phi) + */ +template * = nullptr, + require_arithmetic_t>* = nullptr, + require_t>* = nullptr, + require_eigen_vector_t* = nullptr> +inline double laplace_marginal_density(LLFun&& ll_fun, LLArgs&& ll_args, + Eta&& eta, Theta&& theta_0, + CovarFun&& covariance_function, + CovarArgs&& covar_args, + const laplace_options& options, + std::ostream* msgs) { + return laplace_marginal_density_est( + std::forward(ll_fun), std::forward(ll_args), + std::forward(eta), std::forward(theta_0), + std::forward(covariance_function), + std::forward(covar_args), options, msgs) + .lmd; +} + +/** + * For a latent Gaussian model with global parameters phi, latent + * variables theta, and observations y, this function computes + * an approximation of the log marginal density, p(y | phi). + * This is done by marginalizing out theta, using a Laplace + * approxmation. The latter is obtained by finding the mode, + * using a custom Newton method, and the Hessian of the likelihood. + * + * The convergence criterion for the Newton is a small change in + * the log marginal density. The user controls the tolerance (i.e. + * threshold under which change is deemed small enough) and + * maximum number of steps. + * + * Wrapper for when the global parameter is passed as a double. + * + * @tparam LLFun Type with a valid `operator(Theta, Eta, InnerLLTupleArgs)` + * where `InnerLLTupleArgs` are the elements of `LLTupleArgs` + * @tparam LLTupleArgs A tuple whose elements follow the types required for + * `LLFun` + * @tparam CovarFun Type with a valid `operator(InnerCovarArgs)` where + * `InnerCovarArgs` are a parameter pack of the element types of `CovarArgs` + * @tparam Theta Type derived from `Eigen::EigenBase` with dynamic rows and a + * single column + * @tparam Eta Type derived from `Eigen::EigenBase` with dynamic rows and a + * single column + * @tparam CovarArgs A tuple whose elements follow the types required for + * `CovarFun` + * @param[in] ll_fun A log likelihood functor + * @param[in] ll_args Tuple containing parameters for `LLFun` + * @param[in] covariance_function Functor for the covariance function + * @param[in] eta hyperparameter (input for likelihood) + * @param[in] theta_0 the initial guess for the Laplace optimization + * @param[in,out] msgs stream for messages from likelihood and covariance + * @param[in] options A set of options for tuning the solver + * @param[in] covar_args Tuple of arguments to pass to the covariance matrix + * functor + * @return the log maginal density, p(y | phi) + */ +template * = nullptr, + require_t>* = nullptr, + require_eigen_vector_t* = nullptr> +inline auto laplace_marginal_density(const LLFun& ll_fun, LLTupleArgs&& ll_args, + Eta&& eta, Theta&& theta_0, + CovarFun&& covariance_function, + CovarArgs&& covar_args, + const laplace_options& options, + std::ostream* msgs) { + auto args_refs = to_ref(std::forward(covar_args)); + auto eta_arena = to_arena(eta); + auto md_est = laplace_marginal_density_est( + ll_fun, ll_args, value_of(eta_arena), value_of(theta_0), + covariance_function, value_of(args_refs), options, msgs); + const Eigen::Index theta_size = md_est.theta.size(); + const Eigen::Index eta_size = eta_arena.size(); + using Eta_t = std::decay_t; + // Solver 1, 2 + arena_t R; + // Solver 3 + arena_t LU_solve_covariance; + // Solver 1, 2, 3 + arena_t> partial_parm; + // Solver 1, 2, 3 + arena_t> s2; + if (options.solver == 1) { + // TODO(Steve): Solve without casting from sparse to dense + Eigen::MatrixXd tmp + = md_est.L.template triangularView().solve( + md_est.W_r.toDense()); + R = tmp.transpose() * tmp; + arena_t C = mdivide_left_tri( + md_est.L, md_est.W_r * md_est.covariance); + if (options.hessian_block_size == 1 && eta_size == 0) { + s2 = 0.5 + * (md_est.covariance.diagonal() - (C.transpose() * C).diagonal()) + .cwiseProduct(laplace_likelihood::third_diff( + ll_fun, md_est.theta, value_of(eta_arena), ll_args, msgs)); + } else { + // int block_size = (hessian_block_size == 0) ? hessian_block_size + 1 + // : hessian_block_size; + arena_t A = md_est.covariance - C.transpose() * C; + std::tie(s2, partial_parm) = laplace_likelihood::compute_s2( + ll_fun, md_est.theta, value_of(eta_arena), A, + options.hessian_block_size, ll_args, msgs); + } + } else if (options.solver == 2) { + // TODO(Charles) -- use triangularView for K_root + R = md_est.W_r + - md_est.W_r * md_est.K_root + * md_est.L.transpose() + .template triangularView() + .solve( + md_est.L.template triangularView().solve( + md_est.K_root.transpose() * md_est.W_r)); + + arena_t C + = md_est.L.template triangularView().solve( + md_est.K_root.transpose()); + std::tie(s2, partial_parm) = laplace_likelihood::compute_s2( + ll_fun, md_est.theta, value_of(eta_arena), C.transpose() * C, + options.hessian_block_size, ll_args, msgs); + } else { // options.solver with LU decomposition + LU_solve_covariance = md_est.LU.solve(md_est.covariance); + R = md_est.W_r - md_est.W_r * LU_solve_covariance * md_est.W_r; + + arena_t A + = md_est.covariance + - md_est.covariance * md_est.W_r * LU_solve_covariance; + std::tie(s2, partial_parm) = laplace_likelihood::compute_s2( + ll_fun, md_est.theta, value_of(eta_arena), A, + options.hessian_block_size, ll_args, msgs); + } + auto args_arena = to_arena(args_refs); + if constexpr (is_any_var_scalar_v && !is_constant::value) { + if (eta_size != 0) { + { + const nested_rev_autodiff nested; + Eigen::Matrix K_var + = stan::math::apply( + [&covariance_function, &msgs](auto&&... args) { + return covariance_function(args..., msgs); + }, + args_arena); + var Z + = laplace_pseudo_target(K_var, md_est.a, R, md_est.theta_grad, s2); + set_zero_all_adjoints_nested(); + grad(Z.vi_); + } + auto arg_adj_arena = stan::math::filter_map( + [](auto&& arg) { return to_arena(get_adj(arg)); }, args_arena); + stan::math::for_each([](auto&& arg) { zero_adjoints(arg); }, args_arena); + + arena_t> diff_eta + = md_est.eta_grad; + + arena_t v; + if (options.solver == 1 || options.solver == 2) { + v = md_est.covariance * s2 + - md_est.covariance * R * md_est.covariance * s2; + } else { + v = LU_solve_covariance * s2; + } + if constexpr (Eta_t::RowsAtCompileTime != 0 + && Eta_t::ColsAtCompileTime != 0) { + arena_matrix> eta_adj_arena + = md_est.eta_grad + partial_parm + + laplace_likelihood::diff_eta_implicit( + ll_fun, v, md_est.theta, value_of(eta_arena), ll_args, msgs); + return make_callback_var( + md_est.lmd, [arg_adj_arena, args_arena, eta_arena, + eta_adj_arena](const auto& vi) mutable { + stan::math::for_each( + [&vi](auto&& arg, auto&& arg_adj) { + if constexpr (is_var>::value) { + internal::update_adjoints(arg, arg_adj, vi); + } + }, + args_arena, arg_adj_arena); + internal::update_adjoints(eta_arena, eta_adj_arena, vi); + }); + } + } + } else if constexpr (is_any_var_scalar_v>) { + { + const nested_rev_autodiff nested; + arena_t> K_var + = stan::math::apply( + [&covariance_function, &msgs](auto&&... args) { + return covariance_function(args..., msgs); + }, + args_arena); + // = covariance_function(x, phi_v, delta, delta_int, msgs); + var Z = laplace_pseudo_target(K_var, md_est.a, R, md_est.theta_grad, s2); + set_zero_all_adjoints_nested(); + grad(Z.vi_); + } + auto arg_adj_arena = stan::math::filter_map( + [](auto&& arg) { return to_arena(get_adj(arg)); }, args_arena); + stan::math::for_each([](auto&& arg) { zero_adjoints(arg); }, args_arena); + return make_callback_var( + md_est.lmd, [arg_adj_arena, args_arena](const auto& vi) mutable { + stan::math::for_each( + [&vi](auto&& arg, auto&& arg_adj) { + if constexpr (is_var>::value) { + internal::update_adjoints(arg, arg_adj, vi); + } + }, + args_arena, arg_adj_arena); + }); + } else if (!is_constant::value && eta_size != 0) { + if constexpr (Eta_t::RowsAtCompileTime != 0 + && Eta_t::ColsAtCompileTime != 0) { + arena_t diff_eta = md_est.eta_grad; + + arena_t v; + if (options.solver == 1 || options.solver == 2) { + v = md_est.covariance * s2 + - md_est.covariance * R * md_est.covariance * s2; + } else { + v = LU_solve_covariance * s2; + } + + arena_matrix eta_adj_arena + = md_est.eta_grad + partial_parm + + laplace_likelihood::diff_eta_implicit( + ll_fun, v, md_est.theta, value_of(eta_arena), ll_args, msgs); + + return make_callback_var( + md_est.lmd, [eta_arena, eta_adj_arena](const auto& vi) mutable { + internal::update_adjoints(eta_arena, eta_adj_arena, vi); + }); + } + } + return var(0); +} + +} // namespace math +} // namespace stan + +#endif diff --git a/stan/math/mix/prob.hpp b/stan/math/mix/prob.hpp new file mode 100644 index 00000000000..3f4fd768434 --- /dev/null +++ b/stan/math/mix/prob.hpp @@ -0,0 +1,14 @@ +#ifndef STAN_MATH_MIX_PROB_HPP +#define STAN_MATH_MIX_PROB_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif diff --git a/stan/math/mix/prob/laplace_bernoulli_logit_rng.hpp b/stan/math/mix/prob/laplace_bernoulli_logit_rng.hpp new file mode 100644 index 00000000000..370838bda0e --- /dev/null +++ b/stan/math/mix/prob/laplace_bernoulli_logit_rng.hpp @@ -0,0 +1,129 @@ +#ifndef STAN_MATH_MIX_PROB_LAPLACE_BERNOULLI_LOGIT_RNG_HPP +#define STAN_MATH_MIX_PROB_LAPLACE_BERNOULLI_LOGIT_RNG_HPP + +#include +#include +#include + +namespace stan { +namespace math { + +/** + * In a latent gaussian model, + * + * theta ~ Normal(theta | 0, Sigma(phi)) + * y ~ pi(y | theta) + * + * return a multivariate normal random variate sampled + * from the gaussian approximation of p(theta | y, phi), + * where the likelihood is a Bernoulli with logit link. + * @tparam CovarFun Type of structure for covariance function. + * @tparam ThetaMatrix Type for latent Gaussian variable. + * @tparam RNG Type of rng number. + * @tparam TrainTuple Type for observed/training covariates for covariance fn. + * @tparam PredTuple Type for predictive covariates for covariance fn. + * @tparam Args Type for variadic arguments for likelihood function. + * @param y Vector Vector of total number of trials with a positive outcome. + * @param n_samples Vector of number of trials. + * @param theta_0 Initial guess for mode of Laplace approximation. + * @param covariance_function Covariance function. + * @param train_tuple Observed/training covariates for covariance function. + * @param pred_tuple Predictive covariates for covariance function. + * @param tolerance Tolerared change in objective function for Laplace approx. + * @param max_num_steps Max number of iterations of Newton solver for Laplace + * approx. + * @param hessian_block_size Size of blocks for Hessian of log likelihood w.r.t + * latent Gaussian variables. + * @param solver Type of Newton solver. Each corresponds to a distinct choice + * of B matrix (i.e. application SWM formula): + * 1. computes square-root of negative Hessian. + * 2. computes square-root of covariance matrix. + * 3. computes no square-root and uses LU decomposition. + * @param max_steps_line_search Number of steps after which the algorithm gives + * up on doing a linesearch. If 0, no linesearch. + * @param rng Rng number. + * @param msgs Streaming message for covariance functions. + * @param args Arguments for log likelihood function. + * + */ +template * = nullptr> +inline Eigen::VectorXd // CHECK -- right return type +laplace_marginal_tol_bernoulli_logit_rng( + const std::vector& y, const std::vector& n_samples, + const ThetaMatrix& theta_0, CovarFun&& covariance_function, + CovarArgs&& covar_args, TrainTuple&& train_tuple, PredTuple&& pred_tuple, + const double tolerance, const int64_t max_num_steps, + const int hessian_block_size, const int solver, + const int max_steps_line_search, RNG& rng, std::ostream* msgs) { + laplace_options ops{hessian_block_size, solver, max_steps_line_search, + tolerance, max_num_steps}; + Eigen::Matrix eta_dummy; + return laplace_base_rng( + bernoulli_logit_likelihood{}, + std::forward_as_tuple(to_vector(y), n_samples), covariance_function, + eta_dummy, theta_0, rng, msgs, ops, std::forward(train_tuple), + std::forward(pred_tuple), std::forward(covar_args)); +} + +/** + * In a latent gaussian model, + * + * theta ~ Normal(theta | 0, Sigma(phi)) + * y ~ pi(y | theta) + * + * return a multivariate normal random variate sampled + * from the gaussian approximation of p(theta | y, phi), + * where the likelihood is a Bernoulli with logit link. + * @tparam CovarFun Type of structure for covariance function. + * @tparam ThetaMatrix Type for latent Gaussian variable. + * @tparam RNG Type of rng number. + * @tparam TrainTuple Type for observed/training covariates for covariance fn. + * @tparam PredTuple Type for predictive covariates for covariance fn. + * @tparam Args Type for variadic arguments for likelihood function. + * @param y Vector Vector of total number of trials with a positive outcome. + * @param n_samples Vector of number of trials. + * @param theta_0 Initial guess for mode of Laplace approximation. + * @param covariance_function Covariance function. + * @param train_tuple Observed/training covariates for covariance function. + * @param pred_tuple Predictive covariates for covariance function. + * @param tolerance Tolerared change in objective function for Laplace approx. + * @param max_num_steps Max number of iterations of Newton solver for Laplace + * approx. + * @param hessian_block_size Size of blocks for Hessian of log likelihood w.r.t + * latent Gaussian variables. + * @param solver Type of Newton solver. Each corresponds to a distinct choice + of B matrix (i.e. application SWM formula): + * 1. computes square-root of negative Hessian. + * 2. computes square-root of covariance matrix. + * 3. computes no square-root and uses LU decomposition. + * @param max_steps_line_search Number of steps after which the algorithm gives + * up on doing a linesearch. If 0, no linesearch. + * @param rng Rng number. + * @param msgs Streaming message for covariance and likelihood functions. + * @param args Arguments for log likelihood function. + * + */ +template * = nullptr> +inline Eigen::VectorXd // CHECK -- right return type +laplace_marginal_bernoulli_logit_rng( + const std::vector& y, const std::vector& n_samples, + const ThetaMatrix& theta_0, CovarFun&& covariance_function, + CovarArgs&& covar_args, TrainTuple&& train_tuple, PredTuple&& pred_tuple, + RNG& rng, std::ostream* msgs) { + constexpr laplace_options ops{1, 1, 0, 1e-6, 100}; + Eigen::Matrix eta_dummy; + return laplace_base_rng(bernoulli_logit_likelihood{}, + std::forward_as_tuple(to_vector(y), n_samples), + covariance_function, eta_dummy, theta_0, ops, + std::make_tuple(), std::make_tuple(), rng, msgs, + std::forward(covar_args)); +} + +} // namespace math +} // namespace stan + +#endif diff --git a/stan/math/mix/prob/laplace_marginal_bernoulli_logit_lpmf.hpp b/stan/math/mix/prob/laplace_marginal_bernoulli_logit_lpmf.hpp new file mode 100644 index 00000000000..a504b7deb11 --- /dev/null +++ b/stan/math/mix/prob/laplace_marginal_bernoulli_logit_lpmf.hpp @@ -0,0 +1,104 @@ +#ifndef STAN_MATH_MIX_PROB_LAPLACE_MARGINAL_BERNOULLI_LOGIT_LPMF_HPP +#define STAN_MATH_MIX_PROB_LAPLACE_MARGINAL_BERNOULLI_LOGIT_LPMF_HPP + +#include +#include +#include + +namespace stan { +namespace math { + +struct bernoulli_logit_likelihood { + template + inline stan::return_type_t operator()( + const T_theta& theta, const T_eta& /* eta */, const Eigen::VectorXd& y, + const std::vector& delta_int, std::ostream* pstream) const { + return sum(elt_multiply(theta, y) + - elt_multiply(to_vector(delta_int), log(add(1.0, exp(theta))))); + } +}; + +/** + * Wrapper function around the laplace_marginal function for + * a logistic Bernoulli likelihood. Returns the marginal density + * p(y | phi) by marginalizing out the latent gaussian variable, + * with a Laplace approximation. See the laplace_marginal function + * for more details. + * + * @tparam CovarF Type of structure for covariance function. + * @tparam ThetaMatrix The type of the initial guess, theta_0. + * @tparam Args Type of variadic arguments for likelihood function. + * @param[in] y total counts per group. Second sufficient statistics. + * @param[in] n_samples number of samples per group. First sufficient + * statistics. + * @param[in] theta_0 the initial guess for the Laplace approximation. + * @param covariance_function Covariance function + * @param[in] tolerance controls the convergence criterion when finding + * the mode in the Laplace approximation. + * @param[in] max_num_steps maximum number of steps before the Newton solver + * breaks and returns an error. + * @param hessian_block_size Block size of Hessian of log likelihood w.r.t + * latent Gaussian variable theta. + * @param solver Type of Newton solver. Each corresponds to a distinct choice + * of B matrix (i.e. application SWM formula): + * 1. computes square-root of negative Hessian. + * 2. computes square-root of covariance matrix. + * 3. computes no square-root and uses LU decomposition. + * @param max_steps_line_search Number of steps after which the algorithm gives + * up on doing a linesearch. If 0, no linesearch. + * @param msgs Rng number. + * @param[in] args data for the covariance function. + */ +template * = nullptr> +inline auto laplace_marginal_tol_bernoulli_logit_lpmf( + const std::vector& y, const std::vector& n_samples, + const ThetaMatrix& theta_0, CovarF&& covariance_function, + CovarArgs&& covar_args, double tolerance, int64_t max_num_steps, + const int hessian_block_size, const int solver, + const int max_steps_line_search, std::ostream* msgs) { + Eigen::Matrix eta_dummy; + laplace_options ops{hessian_block_size, solver, max_steps_line_search, + tolerance, max_num_steps}; + return laplace_marginal_density( + bernoulli_logit_likelihood{}, + std::forward_as_tuple(to_vector(y), n_samples), eta_dummy, theta_0, + covariance_function, std::forward(covar_args), ops, msgs); +} + +/** + * Wrapper function around the laplace_marginal function for + * a logistic Bernoulli likelihood. Returns the marginal density + * p(y | phi) by marginalizing out the latent gaussian variable, + * with a Laplace approximation. See the laplace_marginal function + * for more details. + * + * @tparam CovarF Type of structure for covariance function. + * @tparam ThetaMatrix The type of the initial guess, theta_0. + * @tparam Args Arguments for likelihood function. + * @param[in] y total counts per group. Second sufficient statistics. + * @param[in] n_samples number of samples per group. First sufficient + * statistics. + * @param[in] theta_0 the initial guess for the Laplace approximation. + * @param covariance_function + * @param msgs Streaming message for covariance functions. + * @param[in] args data for the covariance function. + */ +template * = nullptr> +inline auto laplace_marginal_bernoulli_logit_lpmf( + const std::vector& y, const std::vector& n_samples, + const ThetaMatrix& theta_0, CovarF&& covariance_function, + CovarArgs&& covar_args, std::ostream* msgs) { + Eigen::Matrix eta_dummy; + constexpr laplace_options ops{1, 1, 0, 1e-6, 100}; + return laplace_marginal_density( + bernoulli_logit_likelihood{}, + std::forward_as_tuple(to_vector(y), n_samples), eta_dummy, theta_0, + covariance_function, std::forward(covar_args), ops, msgs); +} + +} // namespace math +} // namespace stan + +#endif diff --git a/stan/math/mix/prob/laplace_marginal_lpdf.hpp b/stan/math/mix/prob/laplace_marginal_lpdf.hpp new file mode 100644 index 00000000000..4a47708cdc1 --- /dev/null +++ b/stan/math/mix/prob/laplace_marginal_lpdf.hpp @@ -0,0 +1,390 @@ +#ifndef STAN_MATH_MIX_PROB_LAPLACE_MARGINAL_LPDF_HPP +#define STAN_MATH_MIX_PROB_LAPLACE_MARGINAL_LPDF_HPP + +#include +#include + +namespace stan { +namespace math { +/** + * Wrapper function around the laplace_marginal function. + * Returns the marginal density p(y | phi) by marginalizing out + * the latent gaussian variable, with a Laplace approximation. + * See the laplace_marginal function for more details. + * The data y is assumed to be real. + * The function is "overloaded" below for the int y and lpmf case. + * + * @tparam propto If FALSE, log density is computed up to an additive const. + * @tparam LFun The function which returns the log likelihood. + * @tparam LArgs A tuple of arguments to the log likelihood. + * @tparam EtaVec The type of the parameter arguments for the likelihood fn. + * @tparam CovarFun The function which returns the prior covariance matrix. + * @tparam Theta0 The type of the initial guess, theta_0. + * @tparam CovarArgs Arguments supplied to CovarFun. + * @param[in] L_f a function which returns the log likelihood. + * @param[in] l_args A tuple of arguments to pass to the log likelihood. + * @param[in] eta parameter arguments for the log likelihood. + * @param[in] theta_0 initial guess for the Newton solver which returns + * the Laplace approximation. + * @param[in] K_f a function which returns the prior covariance + * for the marginalized out latent Gaussian. + * @param[in] tolerance controls the convergence criterion when finding + * the mode in the Laplace approximation. + * @param[in] max_num_steps maximum number of steps before the Newton solver + * breaks and returns an error. + * @param[in] hessian_block_size the size of the block for a block-diagonal + * Hessian of the log likelihood. If 0, the Hessian is stored + * inside a vector. If the Hessian is dense, this should be the + * size of the Hessian. + * @param solver Type of Newton solver. Each corresponds to a distinct choice + * of B matrix (i.e. application SWM formula): + * 1. computes square-root of negative Hessian. + * 2. computes square-root of covariance matrix. + * 3. computes no square-root and uses LU decomposition. + * @param[in] max_steps_line_search Number of steps after which the algorithm + * gives up on doing a linesearch. If 0, no linesearch. + * @param[in] msgs message stream for the covariance and likelihood function. + * @param[in] covar_args A tuple of arguments for use in the covariance + * function + */ +template * = nullptr> +inline auto laplace_marginal_tol_lpdf( + LFun&& L_f, LArgs&& l_args, const EtaVec& eta, const Theta0& theta_0, + CovarFun&& K_f, CovarArgs&& covar_args, double tolerance, + int64_t max_num_steps, const int hessian_block_size, const int solver, + const int max_steps_line_search, std::ostream* msgs) { + // TEST: provisional signature to agree with parser. + laplace_options ops{hessian_block_size, solver, max_steps_line_search, + tolerance, max_num_steps}; + return laplace_marginal_density( + std::forward(L_f), std::forward(l_args), eta, theta_0, + std::forward(K_f), std::forward(covar_args), ops, + msgs); +} + +/** + * Wrapper function around the laplace_marginal function. + * Returns the marginal density p(y | phi) by marginalizing out + * the latent gaussian variable, with a Laplace approximation. + * See the laplace_marginal function for more details. + * The data y is assumed to be real. + * The function is "overloaded" below for the int y and lpmf case. + * + * @tparam propto If FALSE, log density is computed up to an additive const. + * @tparam LFun The function which returns the log likelihood. + * @tparam LArgs A tuple of arguments to the log likelihood. + * @tparam CovarFun The function which returns the prior covariance matrix. + * @tparam Theta0 The type of the initial guess, theta_0. + * @tparam CovarArgs Arguments supplied to the CovarFun + * @param[in] L_f a function which returns the log likelihood. + * @param[in] l_args A tuple of arguments to pass to the log likelihood + * @param[in] theta_0 initial guess for the Newton solver which returns + * the Laplace approximation. + * @param[in] K_f a function which returns the prior + * covariance for the marginalized out latent Gaussian. + * @param[in] tolerance controls the convergence criterion when finding + * the mode in the Laplace approximation. + * @param[in] max_num_steps maximum number of steps before the Newton solver + * breaks and returns an error. + * @param[in] hessian_block_size the size of the block for a block-diagonal + * Hessian of the log likelihood. If 0, the Hessian is stored + * inside a vector. If the Hessian is dense, this should be the + * size of the Hessian. + * @param[in] solver Type of Newton solver. Each corresponds to a distinct + * choice of B matrix (i.e. application SWM formula): + * 1. computes square-root of negative Hessian. + * 2. computes square-root of covariance matrix. + * 3. computes no square-root and uses LU decomposition. + * @param[in] max_steps_line_search Number of steps after which the algorithm + * gives up on doing a linesearch. If 0, no linesearch. + * @param[in] msgs message stream for the covariance and likelihood function. + * @param[in] covar_args A tuple of arguments for use in the covariance + * function + */ +template * = nullptr> +inline auto laplace_marginal_tol_lpdf( + LFun&& L_f, LArgs&& l_args, const Theta0& theta_0, CovarFun&& K_f, + CovarArgs&& covar_args, double tolerance, int64_t max_num_steps, + const int hessian_block_size, const int solver, + const int max_steps_line_search, std::ostream* msgs) { + laplace_options ops{hessian_block_size, solver, max_steps_line_search, + tolerance, max_num_steps}; + Eigen::Matrix eta; + return laplace_marginal_density( + std::forward(L_f), std::forward(l_args), eta, theta_0, + std::forward(K_f), std::forward(covar_args), ops, + msgs); +} + +/** + * Wrapper function around the laplace_marginal function. + * Returns the marginal density p(y | phi) by marginalizing out + * the latent gaussian variable, with a Laplace approximation. + * See the laplace_marginal function for more details. + * The data y is assumed to be real. + * The function is "overloaded" below for the int y and lpmf case. + * + * @tparam propto If FALSE, log density is computed up to an additive const. + * @tparam LFun The function which returns the log likelihood. + * @tparam LArgs A tuple of arguments to the log likelihood. + * @tparam EtaVec The type of the auxiliary parameter, eta. + * @tparam CovarFun The function which returns the prior covariance matrix. + * @tparam Theta0 The type of the initial guess, theta_0. + * @tparam CovarArgs Arguments supplied to the CovarFun + * @param[in] L_f a function which returns the log likelihood. + * @param[in] l_args A tuple of arguments to pass to the log likelihood + * @param[in] eta non-marginalized parameters for the log likelihood. + * @param[in] theta_0 initial guess for the Newton solver which returns + * the Laplace approximation. + * @param[in] K_f a function which returns the prior + * covariance for the marginalized out latent Gaussian. + * @param[in] tolerance controls the convergence criterion when finding + * the mode in the Laplace approximation. + * @param[in] max_num_steps maximum number of steps before the Newton solver + * breaks and returns an error. + * @param[in] hessian_block_size the size of the block for a block-diagonal + * Hessian of the log likelihood. If 0, the Hessian is stored + * inside a vector. If the Hessian is dense, this should be the + * size of the Hessian. + * @param[in] solver Type of Newton solver. Each corresponds to a distinct + * choice of B matrix (i.e. application SWM formula): + * 1. computes square-root of negative Hessian. + * 2. computes square-root of covariance matrix. + * 3. computes no square-root and uses LU decomposition. + * @param[in] max_steps_line_search Number of steps after which the algorithm + * gives up on doing a linesearch. If 0, no linesearch. + * @param[in] msgs message stream for the covariance and likelihood function. + * @param[in] covar_args A tuple of arguments for use in the covariance + * function + */ +template +inline auto laplace_marginal_tol_lpmf( + LFun&& L_f, LArgs&& l_args, const EtaVec& eta, const Theta0& theta_0, + CovarFun&& K_f, CovarArgs&& covar_args, const double tolerance, + const int64_t max_num_steps, const int hessian_block_size, const int solver, + const int max_steps_line_search, std::ostream* msgs) { + return laplace_marginal_tol_lpdf( + std::forward(L_f), std::forward(l_args), eta, theta_0, + std::forward(K_f), std::forward(covar_args), + tolerance, max_num_steps, hessian_block_size, solver, + max_steps_line_search, msgs); +} + +/** + * Wrapper function around the laplace_marginal function. + * Returns the marginal density p(y | phi) by marginalizing out + * the latent gaussian variable, with a Laplace approximation. + * See the laplace_marginal function for more details. + * The data y is assumed to be real. + * The function is "overloaded" below for the int y and lpmf case. + * + * @tparam propto If FALSE, log density is computed up to an additive const. + * @tparam LFun The function which returns the log likelihood. + * @tparam LArgs A tuple of arguments to the log likelihood. + * @tparam CovarFun The function which returns the prior covariance matrix. + * @tparam Theta0 The type of the initial guess, theta_0. + * @tparam CovarArgs Arguments supplied to the CovarFun + * @param[in] L_f a function which returns the log likelihood. + * @param[in] l_args A tuple of arguments to pass to the log likelihood + * @param[in] theta_0 initial guess for the Newton solver which returns + * the Laplace approximation. + * @param[in] K_f a function which returns the prior + * covariance for the marginalized out latent Gaussian. + * @param[in] tolerance controls the convergence criterion when finding + * the mode in the Laplace approximation. + * @param[in] max_num_steps maximum number of steps before the Newton solver + * breaks and returns an error. + * @param[in] hessian_block_size the size of the block for a block-diagonal + * Hessian of the log likelihood. If 0, the Hessian is stored + * inside a vector. If the Hessian is dense, this should be the + * size of the Hessian. + * @param[in] solver Type of Newton solver. Each corresponds to a distinct + * choice of B matrix (i.e. application SWM formula): + * 1. computes square-root of negative Hessian. + * 2. computes square-root of covariance matrix. + * 3. computes no square-root and uses LU decomposition. + * @param[in] max_steps_line_search Number of steps after which the algorithm + * gives up on doing a linesearch. If 0, no linesearch. + * @param[in] msgs message stream for the covariance and likelihood function. + * @param[in] covar_args A tuple of arguments for use in the covariance + * function + */ +template +inline auto laplace_marginal_tol_lpmf( + LFun&& L_f, LArgs&& l_args, const Theta0& theta_0, CovarFun&& K_f, + CovarArgs&& covar_args, const double tolerance, const int64_t max_num_steps, + const int hessian_block_size, const int solver, + const int max_steps_line_search, std::ostream* msgs) { + return laplace_marginal_tol_lpdf( + std::forward(L_f), std::forward(l_args), theta_0, + std::forward(K_f), std::forward(covar_args), + tolerance, max_num_steps, hessian_block_size, solver, + max_steps_line_search, msgs); +} + +/** + * Wrapper function around the laplace_marginal function. + * Returns the marginal density p(y | phi) by marginalizing out + * the latent gaussian variable, with a Laplace approximation. + * See the laplace_marginal function for more details. + * The data y is assumed to be real. + * The function is "overloaded" below for the int y and lpmf case. + * + * @tparam propto If FALSE, log density is computed up to an additive const. + * @tparam LFun The function which returns the log likelihood. + * @tparam LArgs A tuple of arguments to the log likelihood. + * @tparam EtaVec The type of the auxiliary parameter, eta. + * @tparam CovarFun The function which returns the prior covariance matrix. + * @tparam Theta0 The type of the initial guess, theta_0. + * @tparam CovarArgs Arguments supplied to the CovarFun + * @param[in] L_f a function which returns the log likelihood. + * @param[in] l_args A tuple of arguments to pass to the log likelihood + * @param[in] eta non-marginalized parameters for the log likelihood. + * @param[in] theta_0 initial guess for the Newton solver which returns + * the Laplace approximation. + * @param[in] K_f a function which returns the prior + * covariance for the marginalized out latent Gaussian. + * @param[in] msgs message stream for the covariance and likelihood function. + * @param[in] covar_args A tuple of arguments for use in the covariance + * function + */ +template * = nullptr> +inline auto laplace_marginal_lpdf(LFun&& L_f, LArgs&& l_args, const EtaVec& eta, + const Theta0& theta_0, CovarFun&& K_f, + CovarArgs&& covar_args, std::ostream* msgs) { + constexpr laplace_options ops{1, 1, 0, 1e-6, 100}; + return laplace_marginal_density( + std::forward(L_f), std::forward(l_args), eta, theta_0, + std::forward(K_f), std::forward(covar_args), ops, + msgs); +} + +/** + * Wrapper function around the laplace_marginal function. + * Returns the marginal density p(y | phi) by marginalizing out + * the latent gaussian variable, with a Laplace approximation. + * See the laplace_marginal function for more details. + * The data y is assumed to be real. + * The function is "overloaded" below for the int y and lpmf case. + * + * @tparam propto If FALSE, log density is computed up to an additive const. + * @tparam LFun The function which returns the log likelihood. + * @tparam LArgs A tuple of arguments to the log likelihood. + * @tparam CovarFun The function which returns the prior covariance matrix. + * @tparam Theta0 The type of the initial guess, theta_0. + * @tparam CovarArgs Arguments supplied to the CovarFun + * @param[in] L_f a function which returns the log likelihood. + * @param[in] l_args A tuple of arguments to pass to the log likelihood + * @param[in] theta_0 initial guess for the Newton solver which returns + * the Laplace approximation. + * @param[in] K_f a function which returns the prior + * covariance for the marginalized out latent Gaussian. + * @param[in] msgs message stream for the covariance and likelihood function. + * @param[in] covar_args A tuple of arguments for use in the covariance + * function + */ +template * = nullptr> +inline auto laplace_marginal_lpdf(LFun&& L_f, LArgs&& l_args, + const Theta0& theta_0, CovarFun&& K_f, + CovarArgs&& covar_args, std::ostream* msgs) { + // TEST: provisional signature to agree with parser. + Eigen::Matrix eta; + constexpr laplace_options ops{1, 1, 0, 1e-6, 100}; + return laplace_marginal_density( + std::forward(L_f), std::forward(l_args), eta, theta_0, + std::forward(K_f), std::forward(covar_args), ops, + msgs); +} + +/** + * Wrapper function around the laplace_marginal function. + * Returns the marginal density p(y | phi) by marginalizing out + * the latent gaussian variable, with a Laplace approximation. + * See the laplace_marginal function for more details. + * The data y is assumed to be real. + * The function is "overloaded" below for the int y and lpmf case. + * + * @tparam propto If FALSE, log density is computed up to an additive const. + * @tparam LFun The function which returns the log likelihood. + * @tparam LArgs A tuple of arguments to the log likelihood. + * @tparam EtaVec The type of the auxiliary parameter, eta. + * @tparam CovarFun The function which returns the prior covariance matrix. + * @tparam Theta0 The type of the initial guess, theta_0. + * @tparam CovarArgs Arguments supplied to the CovarFun + * @param[in] L_f a function which returns the log likelihood. + * @param[in] l_args A tuple of arguments to pass to the log likelihood + * @param[in] eta non-marginalized parameters for the log likelihood. + * @param[in] theta_0 initial guess for the Newton solver which returns + * the Laplace approximation. + * @param[in] K_f a function which returns the prior + * covariance for the marginalized out latent Gaussian. + * @param[in] msgs message stream for the covariance and likelihood function. + * @param[in] covar_args A tuple of arguments for use in the covariance + * function + */ +template * = nullptr> +inline auto laplace_marginal_lpmf(LFun&& L_f, LArgs&& l_args, const EtaVec& eta, + const Theta0& theta_0, CovarFun&& K_f, + CovarArgs&& covar_args, std::ostream* msgs) { + constexpr laplace_options ops{1, 1, 0, 1e-6, 100}; + return laplace_marginal_density( + std::forward(L_f), std::forward(l_args), eta, theta_0, + std::forward(K_f), std::forward(covar_args), ops, + msgs); +} + +/** + * Wrapper function around the laplace_marginal function. + * Returns the marginal density p(y | phi) by marginalizing out + * the latent gaussian variable, with a Laplace approximation. + * See the laplace_marginal function for more details. + * The data y is assumed to be real. + * The function is "overloaded" below for the int y and lpmf case. + * + * @tparam propto If FALSE, log density is computed up to an additive const. + * @tparam LFun The function which returns the log likelihood. + * @tparam LArgs A tuple of arguments to the log likelihood. + * @tparam CovarFun The function which returns the prior covariance matrix. + * @tparam Theta0 The type of the initial guess, theta_0. + * @tparam CovarArgs Arguments supplied to the CovarFun + * @param[in] L_f a function which returns the log likelihood. + * @param[in] l_args A tuple of arguments to pass to the log likelihood + * @param[in] theta_0 initial guess for the Newton solver which returns + * the Laplace approximation. + * @param[in] K_f a function which returns the prior + * covariance for the marginalized out latent Gaussian. + * @param[in] msgs message stream for the covariance and likelihood function. + * @param[in] covar_args A tuple of arguments for use in the covariance + * function + */ +template * = nullptr> +inline auto laplace_marginal_lpmf(LFun&& L_f, LArgs&& l_args, + const Theta0& theta_0, CovarFun&& K_f, + CovarArgs&& covar_args, std::ostream* msgs) { + // TEST: provisional signature to agree with parser. + Eigen::Matrix eta; + constexpr laplace_options ops{1, 1, 0, 1e-6, 100}; + return laplace_marginal_density( + std::forward(L_f), std::forward(l_args), eta, theta_0, + std::forward(K_f), std::forward(covar_args), ops, + msgs); +} + +} // namespace math +} // namespace stan + +#endif diff --git a/stan/math/mix/prob/laplace_marginal_neg_binomial_2_log_lpmf.hpp b/stan/math/mix/prob/laplace_marginal_neg_binomial_2_log_lpmf.hpp new file mode 100644 index 00000000000..bbda873e52d --- /dev/null +++ b/stan/math/mix/prob/laplace_marginal_neg_binomial_2_log_lpmf.hpp @@ -0,0 +1,127 @@ +#ifndef STAN_MATH_MIX_PROB_LAPLACE_MARGINAL_NEG_BINOMIAL_2_LOG_LPMF_HPP +#define STAN_MATH_MIX_PROB_LAPLACE_MARGINAL_NEG_BINOMIAL_2_LOG_LPMF_HPP + +#include +#include + +namespace stan { +namespace math { + +struct neg_binomial_2_log_likelihood { + template + inline return_type_t operator()( + const Eigen::Matrix& theta, + const Eigen::Matrix& eta, Y_t&& y, + Sums_t&& sums, NSamples&& n_samples) const { + T_eta eta_scalar = eta(0); + return_type_t logp = 0; + for (size_t i = 0; i < y.size(); i++) { + logp += binomial_coefficient_log(y(i) + eta_scalar - 1, y(i)); + } + // CHECK -- is it better to vectorize this loop? + Eigen::Matrix exp_theta = exp(theta); + for (Eigen::Index i = 0; i < theta.size(); i++) { + return_type_t log_eta_plus_exp_theta + = log(eta_scalar + exp_theta(i)); + logp += sums(i) * (theta(i) - log_eta_plus_exp_theta) + + n_samples(i) * eta_scalar + * (log(eta_scalar) - log_eta_plus_exp_theta); + } + return logp; + } +}; + +/** + * Wrapper function around the laplace_marginal function for + * a negative binomial likelihood. Uses the 2nd parameterization. + * Returns the marginal density p(y | phi) by marginalizing + * out the latent gaussian variable, with a Laplace approximation. + * See the laplace_marginal function for more details. + * + * @tparam CovarFun The type of the initial guess, theta_0. + * @tparam Eta The type for the global parameter, phi. + * @tparam Theta0 The type of the initial guess, theta_0. + * @tparam Args The type of arguments for the covariance function. + * @param[in] y observations. + * @param[in] y_index group to which each observation belongs. Each group + * is parameterized by one element of theta. + * @param[in] n_samples + * @param[in] sums + * @param[in] eta non-marginalized model parameters for the likelihood. + * @param[in] theta_0 the initial guess for the Laplace approximation. + * @param[in] covariance_function a function which returns the prior covariance. + * @param[in] tolerance controls the convergence criterion when finding + * the mode in the Laplace approximation. + * @param[in] max_num_steps maximum number of steps before the Newton solver + * breaks and returns an error. + * @param[in] hessian_block_size the size of the block for a block-diagonal + * Hessian of the log likelihood. If 0, the Hessian is stored + * inside a vector. If the Hessian is dense, this should be the + * size of the Hessian. + * @param[in] solver Type of Newton solver. Each corresponds to a distinct + * choice of B matrix (i.e. application SWM formula): + * 1. computes square-root of negative Hessian. + * 2. computes square-root of covariance matrix. + * 3. computes no square-root and uses LU decomposition. + * @param[in] max_steps_line_search Number of steps after which the algorithm + * gives up on doing a linesearch. If 0, no linesearch. + * @param[in] msgs message stream for the covariance and likelihood function. + * @param[in] args model parameters and data for the covariance functor. + */ +template +inline auto laplace_marginal_tol_neg_binomial_2_log_lpmf( + const std::vector& y, const std::vector& y_index, + const Eigen::VectorXd& n_samples, const Eigen::VectorXd& sums, + const Eta& eta, const Theta0& theta_0, CovarFun&& covariance_function, + CovarArgs&& covar_args, double tolerance, int64_t max_num_steps, + const int hessian_block_size, const int solver, + const int max_steps_line_search, std::ostream* msgs) { + laplace_options ops{hessian_block_size, solver, max_steps_line_search, + tolerance, max_num_steps}; + return laplace_marginal_density( + neg_binomial_2_log_likelihood{}, + std::forward_as_tuple(to_vector(y), y_index, n_samples, sums), eta, + theta_0, std::forward(covariance_function), + std::forward(covar_args), ops, msgs); +} + +/** + * Wrapper function around the laplace_marginal function for + * a negative binomial likelihood. Uses the 2nd parameterization. + * Returns the marginal density p(y | phi) by marginalizing + * out the latent gaussian variable, with a Laplace approximation. + * See the laplace_marginal function for more details. + * + * @tparam CovarFun The type of the initial guess, theta_0. + * @tparam Theta0 The type of the initial guess, theta_0. + * @tparam Eta The type of parameter arguments for the likelihood function. + * @tparam Args Type of variadic arguments for covariance function. + * @param[in] y observations. + * @param[in] y_index group to which each observation belongs. Each group + * is parameterized by one element of theta. + * @param[in] n_samples Number of count observations per group. + * @param[in] sums Total number of counts per group. + * @param[in] eta Parameter argument for likelihood function. + * @param[in] theta_0 the initial guess for the Laplace approximation. + * @param[in] covariance_function a function which returns the prior covariance. + * @param[in] msgs message stream for the covariance and likelihood function. + * @param[in] args model parameters and data for the covariance functor. + */ +template +inline auto laplace_marginal_neg_binomial_2_log_lpmf( + const std::vector& y, const std::vector& y_index, + const Eigen::VectorXd& n_samples, const Eigen::VectorXd& sums, + const Eta& eta, const Theta0& theta_0, CovarFun&& covariance_function, + CovarArgs&& covar_args, std::ostream* msgs) { + constexpr laplace_options ops{1, 1, 0, 1e-6, 100}; + return laplace_marginal_density( + neg_binomial_2_log_likelihood{}, + std::forward_as_tuple(to_vector(y), y_index, n_samples, sums), eta, + theta_0, std::forward(covariance_function), + std::forward(covar_args), ops, msgs); +} +} // namespace math +} // namespace stan + +#endif diff --git a/stan/math/mix/prob/laplace_marginal_poisson_log_exposure_lpmf.hpp b/stan/math/mix/prob/laplace_marginal_poisson_log_exposure_lpmf.hpp new file mode 100644 index 00000000000..c2f23f891f8 --- /dev/null +++ b/stan/math/mix/prob/laplace_marginal_poisson_log_exposure_lpmf.hpp @@ -0,0 +1,133 @@ +#ifndef STAN_MATH_MIX_PROB_LAPLACE_MARGINAL_POISSON_LOG_EXPOSURE_LPMF_HPP +#define STAN_MATH_MIX_PROB_LAPLACE_MARGINAL_POISSON_LOG_EXPOSURE_LPMF_HPP + +#include +#include +#include +#include + +namespace stan { +namespace math { + +struct poisson_log_exposure_likelihood { + /** + * Returns the lpmf for a Poisson with a log link across + * multiple groups. No need to compute the log normalizing constant. + * Same as above, but includes a exposure term to correct the + * log rate for each group. + * @tparam Theta Type of the log Poisson rate. + * @tparam Eta Type of the auxiliary parameter (not used here). + * @param[in] theta log Poisson rate for each group. + * @param[in] y First n elements contain the sum of counts in each group + * @param[in] ye next n elements the exposure in each group, where n is the + * number of groups. + * @param[in] delta_int number of observations in each group. + * return lpmf for a Poisson with a log link. + * @param pstream + */ + template * = nullptr, + require_eigen_t* = nullptr> + inline auto operator()(const Theta& theta, const Eta& /* eta */, + const Eigen::VectorXd& y, Eigen::VectorXd ye, + const std::vector& delta_int, + std::ostream* pstream) const { + auto n_samples = to_vector(delta_int); + auto shifted_mean = to_ref(theta + log(ye)); + return -sum(lgamma(add(y, 1))) + dot_product(shifted_mean, y) + - dot_product(n_samples, exp(shifted_mean)); + } +}; + +/** + * Wrapper function around the laplace_marginal function for + * a log poisson likelihood. Returns the marginal density + * p(y | phi) by marginalizing out the latent gaussian variable, + * with a Laplace approximation. See the laplace_marginal function + * for more details. + * + * @tparam CovarFun The type of the initial guess, theta_0. + * @tparam YeVec The type for the global parameter, phi. + * @tparam Theta0 The type of the initial guess, theta_0. + * @tparam Args The type of variadic arguments for the covariance function. + * @param[in] y total counts per group. Second sufficient statistics. + * @param[in] n_samples number of samples per group. First sufficient + * statistics. + * @param[in] ye + * @param[in] theta_0 the initial guess for the Laplace approximation. + * @param[in] covariance_function a function which returns the prior covariance. + * @param[in] tolerance controls the convergence criterion when finding + * the mode in the Laplace approximation. + * @param[in] max_num_steps maximum number of steps before the Newton solver + * breaks and returns an error. + * @param[in] hessian_block_size the size of the block for a block-diagonal + * Hessian of the log likelihood. If 0, the Hessian is stored + * inside a vector. If the Hessian is dense, this should be the + * size of the Hessian. + * @param[in] solver + * @param[in] max_steps_line_search + * @param[in] msgs + * @param[in] args model parameters and data for the covariance functor. + */ +template * = nullptr> +inline auto laplace_marginal_tol_poisson_2_log_lpmf( + const std::vector& y, const std::vector& n_samples, + const YeVec& ye, const ThetaVec& theta_0, CovarFun&& covariance_function, + CovarArgs&& covar_args, double tolerance, int64_t max_num_steps, + const int hessian_block_size, const int solver, + const int max_steps_line_search, std::ostream* msgs) { + Eigen::Matrix eta_dummy; + Eigen::VectorXd y_vec = to_vector(y); + Eigen::VectorXd y_and_ye(y_vec.size() + ye.size()); + y_and_ye << y_vec, ye; + laplace_options ops{hessian_block_size, solver, max_steps_line_search, + tolerance, max_num_steps}; + return laplace_marginal_density( + poisson_log_exposure_likelihood{}, + std::forward_as_tuple(to_vector(y), ye, n_samples), eta_dummy, theta_0, + std::forward(covariance_function), + std::forward(covar_args), ops, msgs); +} + +/** + * Wrapper function around the laplace_marginal function for + * a log poisson likelihood. Returns the marginal density + * p(y | phi) by marginalizing out the latent gaussian variable, + * with a Laplace approximation. See the laplace_marginal function + * for more details. + * + * @tparam CovarFun The type of the initial guess, theta_0. + * @tparam YeVec The type for the global parameter, phi. + * @tparam ThetaVec The type of the initial guess, theta_0. + * @tparam Args + * @param[in] y total counts per group. Second sufficient statistics. + * @param[in] n_samples number of samples per group. First sufficient + * statistics. + * @param[in] ye + * @param[in] theta_0 the initial guess for the Laplace approximation. + * @param[in] covariance_function a function which returns the prior covariance. + * @param[in] msgs + * @param[in] args model parameters and data for the covariance functor. + */ +template * = nullptr> +inline auto laplace_marginal_poisson_2_log_lpmf( + const std::vector& y, const std::vector& n_samples, + const YeVec& ye, const ThetaVec& theta_0, CovarFun&& covariance_function, + CovarArgs&& covar_args, std::ostream* msgs) { + Eigen::Matrix eta_dummy; + constexpr laplace_options ops{1, 1, 0, 1e-6, 100}; + return laplace_marginal_density( + poisson_log_exposure_likelihood{}, + std::forward_as_tuple(to_vector(y), ye, n_samples), eta_dummy, theta_0, + std::forward(covariance_function), + std::forward(covar_args), ops, msgs); +} + +} // namespace math +} // namespace stan + +#endif diff --git a/stan/math/mix/prob/laplace_marginal_poisson_log_lpmf.hpp b/stan/math/mix/prob/laplace_marginal_poisson_log_lpmf.hpp new file mode 100644 index 00000000000..b77e96d19b2 --- /dev/null +++ b/stan/math/mix/prob/laplace_marginal_poisson_log_lpmf.hpp @@ -0,0 +1,100 @@ +#ifndef STAN_MATH_MIX_PROB_LAPLACE_MARGINAL_POISSON_LOG_LPMF_HPP +#define STAN_MATH_MIX_PROB_LAPLACE_MARGINAL_POISSON_LOG_LPMF_HPP + +#include +#include +#include +#include + +namespace stan { +namespace math { + +struct poisson_log_likelihood { + /** + * Returns the lpmf for a Poisson with a log link across + * multiple groups. No need to compute the log normalizing constant. + * @tparam T_theta Type of the log Poisson rate. + * @tparam T_eta Type of the auxiliary parameter (not used here). + * @param[in] theta log Poisson rate for each group. + * @param[in] y sum of counts in each group. + * @param[in] delta_int number of observations in each group. + * return lpmf for a Poisson with a log link. + * @param[in] pstream + */ + template * = nullptr, + require_eigen_t* = nullptr> + inline auto operator()(const Theta& theta, const Eta& /* eta */, + const Eigen::VectorXd& y, + const std::vector& delta_int, + std::ostream* pstream) const { + auto n_samples = to_vector(delta_int); + return -sum(lgamma(add(y, 1))) + dot_product(theta, y) + - dot_product(n_samples, exp(theta)); + } +}; + +/** + * Wrapper function around the laplace_marginal function for + * a log poisson likelihood. Returns the marginal density + * p(y | phi) by marginalizing out the latent gaussian variable, + * with a Laplace approximation. See the laplace_marginal function + * for more details. + * + * @tparam T0 The type of the initial guess, theta_0. + * @tparam T1 The type for the global parameter, phi. + * @param[in] y total counts per group. Second sufficient statistics. + * @param[in] n_samples number of samples per group. First sufficient + * statistics. + * @param[in] theta_0 the initial guess for the Laplace approximation. + * @param[in] covariance_function a function which returns the prior covariance. + * @param[in] tolerance controls the convergence criterion when finding + * the mode in the Laplace approximation. + * @param[in] max_num_steps maximum number of steps before the Newton solver + * breaks and returns an error. + * @param[in] hessian_block_size the size of the block for a block-diagonal + * Hessian of the log likelihood. If 0, the Hessian is stored + * inside a vector. If the Hessian is dense, this should be the + * size of the Hessian. + * @param[in] solver + * @param[in] max_steps_line_search + * @param[in] msgs + * @param[in] args model parameters and data for the covariance functor. + */ +template * = nullptr> +inline auto laplace_marginal_tol_poisson_log_lpmf( + const std::vector& y, const std::vector& n_samples, + const ThetaVec& theta_0, CovarFun&& covariance_function, + CovarArgs&& covar_args, double tolerance, int64_t max_num_steps, + const int hessian_block_size, const int solver, + const int max_steps_line_search, std::ostream* msgs) { + Eigen::Matrix eta_dummy; + laplace_options ops{hessian_block_size, solver, max_steps_line_search, + tolerance, max_num_steps}; + return laplace_marginal_density( + poisson_log_likelihood{}, std::forward_as_tuple(to_vector(y), n_samples), + eta_dummy, theta_0, covariance_function, + std::forward(covar_args), ops, msgs); +} + +template * = nullptr> +inline auto laplace_marginal_poisson_log_lpmf(const std::vector& y, + const std::vector& n_samples, + const ThetaVec& theta_0, + CovarFun&& covariance_function, + CovarArgs&& covar_args, + std::ostream* msgs) { + Eigen::Matrix eta_dummy; + constexpr laplace_options ops{1, 1, 0, 1e-6, 100}; + return laplace_marginal_density( + poisson_log_likelihood{}, std::forward_as_tuple(to_vector(y), n_samples), + eta_dummy, theta_0, covariance_function, + std::forward(covar_args), ops, msgs); +} + +} // namespace math +} // namespace stan + +#endif diff --git a/stan/math/mix/prob/laplace_poisson_log_exposure_rng.hpp b/stan/math/mix/prob/laplace_poisson_log_exposure_rng.hpp new file mode 100644 index 00000000000..8334da069f4 --- /dev/null +++ b/stan/math/mix/prob/laplace_poisson_log_exposure_rng.hpp @@ -0,0 +1,111 @@ +#ifndef STAN_MATH_MIX_PROB_LAPLACE_POISSON_LOG_EXPOSURE_RNG_HPP +#define STAN_MATH_MIX_PROB_LAPLACE_POISSON_LOG_EXPOSURE_RNG_HPP + +#include +#include +#include + +namespace stan { +namespace math { + +/** + * Wrapper function around the laplace_marginal function for + * a log poisson likelihood with exposure. Returns the marginal density + * p(y | phi) by marginalizing out the latent gaussian variable, + * with a Laplace approximation. See the laplace_marginal function + * for more details. + * + * @tparam CovarFun + * @tparam ThetaMatrix + * @tparam RNG + * @tparam TrainTuple + * @tparam PredTuple + * @tparam Args + * @param y + * @param n_samples + * @param ye + * @param theta_0 + * @param covariance_function + * @param train_tuple + * @param pred_tuple + * @param tolerance + * @param max_num_steps + * @param hessian_block_size + * @param solver + * @param max_steps_line_search + * @param rng + * @param msgs + * @param args + * + */ +template * = nullptr> +inline auto // CHECK -- right return type +laplace_marginal_tol_poisson_2_log_rng( + const std::vector& y, const std::vector& n_samples, + const Eigen::VectorXd& ye, const ThetaMatrix& theta_0, + CovarFun&& covariance_function, CovarArgs&& covar_args, + TrainTuple&& train_tuple, PredTuple&& pred_tuple, const double tolerance, + const int64_t max_num_steps, const int hessian_block_size, const int solver, + const int max_steps_line_search, RNG& rng, std::ostream* msgs) { + Eigen::Matrix eta_dummy; + laplace_options ops{hessian_block_size, solver, max_steps_line_search, + tolerance, max_num_steps}; + return laplace_base_rng(poisson_log_exposure_likelihood{}, + std::forward_as_tuple(y, ye, n_samples), + covariance_function, eta_dummy, theta_0, ops, + std::forward(train_tuple), + std::forward(pred_tuple), rng, msgs, + std::forward(covar_args)); +} + +/** + * Wrapper function around the laplace_marginal function for + * a log poisson likelihood with exposure. Returns the marginal density + * p(y | phi) by marginalizing out the latent gaussian variable, + * with a Laplace approximation. See the laplace_marginal function + * for more details. + * + * @tparam CovarFun + * @tparam ThetaMatrix + * @tparam RNG + * @tparam TrainTuple + * @tparam PredTuple + * @tparam Args + * @param y + * @param n_samples + * @param ye + * @param theta_0 + * @param covariance_function + * @param train_tuple + * @param pred_tuple + * @param rng + * @param msgs + * @param args + * + */ +template * = nullptr> +inline auto // TODO(Steve): Allow scalar or std vector return +laplace_marginal_poisson_2_log_rng( + const std::vector& y, const std::vector& n_samples, + const Eigen::VectorXd& ye, const ThetaMatrix& theta_0, + CovarFun&& covariance_function, CovarArgs&& covar_args, + TrainTuple&& train_tuple, PredTuple&& pred_tuple, RNG& rng, + std::ostream* msgs) { + Eigen::Matrix eta_dummy; + constexpr laplace_options ops{1, 1, 0, 1e-6, 100}; + return laplace_base_rng(poisson_log_exposure_likelihood{}, + std::forward_as_tuple(to_vector(y), ye, n_samples), + covariance_function, eta_dummy, theta_0, ops, + std::forward(train_tuple), + std::forward(pred_tuple), rng, msgs, + std::forward(covar_args)); +} + +} // namespace math +} // namespace stan + +#endif diff --git a/stan/math/mix/prob/laplace_poisson_log_rng.hpp b/stan/math/mix/prob/laplace_poisson_log_rng.hpp new file mode 100644 index 00000000000..eb53dfefa16 --- /dev/null +++ b/stan/math/mix/prob/laplace_poisson_log_rng.hpp @@ -0,0 +1,111 @@ +#ifndef STAN_MATH_MIX_PROB_LAPLACE_POISSON_LOG_RNG_HPP +#define STAN_MATH_MIX_PROB_LAPLACE_POISSON_LOG_RNG_HPP + +#include +#include +#include +#include + +namespace stan { +namespace math { + +/** + * In a latent gaussian model, + * + * theta ~ Normal(theta | 0, Sigma(phi)) + * y ~ pi(y | theta) + * + * return a multivariate normal random variate sampled + * from the gaussian approximation of p(theta | y, phi) + * where the likelihood is a Poisson with a log link. + * @tparam CovarFun + * @tparam ThetaMatrix + * @tparam RNG + * @tparam TrainTuple + * @tparam PredTuple + * @tparam Args + * @param y + * @param n_samples + * @param theta_0 + * @param covariance_function + * @param train_tuple + * @param pred_tuple + * @param tolerance + * @param max_num_steps + * @param hessian_block_size + * @param solver + * @param max_steps_line_search + * @param rng + * @param msgs + * @param args + * + */ +template * = nullptr> +inline Eigen::VectorXd laplace_marginal_tol_poisson_log_rng( + const std::vector& y, const std::vector& n_samples, + const ThetaMatrix& theta_0, CovarFun&& covariance_function, + CovarArgs&& covar_args, TrainTuple&& train_tuple, PredTuple&& pred_tuple, + const double tolerance, const int64_t max_num_steps, + const int hessian_block_size, const int solver, + const int max_steps_line_search, RNG& rng, std::ostream* msgs) { + Eigen::VectorXd eta_dummy; + laplace_options ops{hessian_block_size, solver, max_steps_line_search, + tolerance, max_num_steps}; + return laplace_base_rng(poisson_log_likelihood{}, + std::forward_as_tuple(to_vector(y), n_samples), + covariance_function, eta_dummy, theta_0, ops, + std::forward(train_tuple), + std::forward(pred_tuple), rng, msgs, + std::forward(covar_args)); +} + +/** + * In a latent gaussian model, + * + * theta ~ Normal(theta | 0, Sigma(phi)) + * y ~ pi(y | theta) + * + * return a multivariate normal random variate sampled + * from the gaussian approximation of p(theta | y, phi) + * where the likelihood is a Poisson with a log link. + * @tparam CovarFun + * @tparam ThetaMatrix + * @tparam RNG + * @tparam TrainTuple + * @tparam PredTuple + * @tparam Args + * @param y + * @param n_samples + * @param theta_0 + * @param covariance_function + * @param train_tuple + * @param pred_tuple + * @param rng + * @param msgs + * @param args + * + */ +template * = nullptr> +inline Eigen::VectorXd laplace_marginal_poisson_log_rng( + const std::vector& y, const std::vector& n_samples, + const ThetaMatrix& theta_0, CovarFun&& covariance_function, + CovarArgs&& covar_args, TrainTuple&& train_tuple, PredTuple&& pred_tuple, + RNG& rng, std::ostream* msgs) { + Eigen::VectorXd eta_dummy; + constexpr laplace_options ops{1, 1, 0, 1e-6, 100}; + return laplace_base_rng(poisson_log_likelihood{}, + std::forward_as_tuple(to_vector(y), n_samples), + covariance_function, eta_dummy, theta_0, ops, + std::forward(train_tuple), + std::forward(pred_tuple), rng, msgs, + std::forward(covar_args)); +} + +} // namespace math +} // namespace stan + +#endif diff --git a/stan/math/mix/prob/laplace_rng.hpp b/stan/math/mix/prob/laplace_rng.hpp new file mode 100644 index 00000000000..b3be34561ab --- /dev/null +++ b/stan/math/mix/prob/laplace_rng.hpp @@ -0,0 +1,204 @@ +#ifndef STAN_MATH_MIX_PROB_LAPLACE_RNG_HPP +#define STAN_MATH_MIX_PROB_LAPLACE_RNG_HPP + +#include +#include +#include + +namespace stan { +namespace math { + +/** + * In a latent gaussian model, + * + * theta ~ Normal(theta | 0, Sigma(phi)) + * y ~ pi(y | theta) + * + * return a multivariate normal random variate sampled + * from the gaussian approximation of p(theta | y, phi) + * where the log likelihood is given by L_f. + * @tparam LFun + * @tparam LArgs + * @tparam ThetaVec + * @tparam EtaVec + * @tparam CovarFun + * @tparam RNG + * @tparam TrainTuple + * @tparam PredTuple + * @tparam Args + * @param L_f + * @param l_args + * @param eta + * @param K_f + * @param theta_0 + * @param tolerance + * @param max_num_steps + * @param hessian_block_size + * @param solver + * @param max_steps_line_search + * @param rng + * @param msgs + * @param train_tuple + * @param pred_tuple + * @param args + */ +template +inline Eigen::VectorXd laplace_marginal_tol_rng( + LFun&& L_f, LArgs&& l_args, const EtaVec& eta, const double tolerance, + const int64_t max_num_steps, const int hessian_block_size, const int solver, + const int max_steps_line_search, const ThetaVec& theta_0, CovarFun&& K_f, + CovarArgs&& covar_args, TrainTuple&& train_tuple, PredTuple&& pred_tuple, + RNG& rng, std::ostream* msgs) { + const laplace_options ops{hessian_block_size, solver, max_steps_line_search, + tolerance, max_num_steps}; + return laplace_base_rng(std::forward(L_f), l_args, K_f, eta, theta_0, + ops, std::forward(train_tuple), + std::forward(pred_tuple), rng, msgs, + std::forward(covar_args)); +} + +/** + * In a latent gaussian model, + * + * theta ~ Normal(theta | 0, Sigma(phi)) + * y ~ pi(y | theta) + * + * return a multivariate normal random variate sampled + * from the gaussian approximation of p(theta | y, phi) + * where the log likelihood is given by L_f. + * @tparam LFun + * @tparam LArgs + * @tparam ThetaVec + * @tparam EtaVec + * @tparam CovarFun + * @tparam RNG + * @tparam TrainTuple + * @tparam PredTuple + * @tparam Args + * @param L_f + * @param l_args + * @param K_f + * @param theta_0 + * @param tolerance + * @param max_num_steps + * @param hessian_block_size + * @param solver + * @param max_steps_line_search + * @param rng + * @param msgs + * @param train_tuple + * @param pred_tuple + * @param args + */ +template +inline Eigen::VectorXd laplace_marginal_tol_rng( + LFun&& L_f, LArgs&& l_args, const ThetaVec& theta_0, CovarFun&& K_f, + CovarArgs&& covar_args, TrainTuple&& train_tuple, PredTuple&& pred_tuple, + const double tolerance, const int64_t max_num_steps, + const int hessian_block_size, const int solver, + const int max_steps_line_search, RNG& rng, std::ostream* msgs) { + const laplace_options ops{hessian_block_size, solver, max_steps_line_search, + tolerance, max_num_steps}; + Eigen::Matrix eta; + return laplace_base_rng(std::forward(L_f), std::forward(l_args), + K_f, eta, theta_0, ops, + std::forward(train_tuple), + std::forward(pred_tuple), rng, msgs, + std::forward(covar_args)); +} + +/** + * In a latent gaussian model, + * + * theta ~ Normal(theta | 0, Sigma(phi)) + * y ~ pi(y | theta) + * + * return a multivariate normal random variate sampled + * from the gaussian approximation of p(theta | y, phi) + * where the log likelihood is given by L_f. + * @tparam LFun + * @tparam LArgs + * @tparam ThetaVec + * @tparam EtaVec + * @tparam CovarFun + * @tparam RNG + * @tparam TrainTuple + * @tparam PredTuple + * @tparam Args + * @param L_f + * @param l_args + * @param eta + * @param K_f + * @param theta_0 + * @param rng + * @param msgs + * @param train_tuple + * @param pred_tuple + * @param args + */ +template +inline Eigen::VectorXd laplace_marginal_rng( + LFun&& L_f, LArgs&& l_args, const EtaVec& eta, const ThetaVec& theta_0, + CovarFun&& K_f, CovarArgs&& covar_args, TrainTuple&& train_tuple, + PredTuple&& pred_tuple, RNG& rng, std::ostream* msgs) { + constexpr laplace_options ops{1, 1, 0, 1e-6, 100}; + return laplace_base_rng(std::forward(L_f), std::forward(l_args), + K_f, eta, theta_0, ops, + std::forward(train_tuple), + std::forward(pred_tuple), rng, msgs, + std::forward(covar_args)); +} + +/** + * In a latent gaussian model, + * + * theta ~ Normal(theta | 0, Sigma(phi)) + * y ~ pi(y | theta) + * + * return a multivariate normal random variate sampled + * from the gaussian approximation of p(theta | y, phi) + * where the log likelihood is given by L_f. + * @tparam LFun + * @tparam LArgs + * @tparam ThetaVec + * @tparam CovarFun + * @tparam RNG + * @tparam TrainTuple + * @tparam PredTuple + * @tparam Args + * @param L_f + * @param l_args + * @param K_f + * @param theta_0 + * @param train_tuple + * @param pred_tuple + * @param rng + * @param msgs + * @param args + */ +template +inline Eigen::VectorXd laplace_marginal_rng( + LFun&& L_f, LArgs&& l_args, const ThetaVec& theta_0, CovarFun&& K_f, + CovarArgs&& covar_args, TrainTuple&& train_tuple, PredTuple&& pred_tuple, + RNG& rng, std::ostream* msgs) { + constexpr laplace_options ops{1, 1, 0, 1e-6, 100}; + Eigen::Matrix eta; + return laplace_base_rng(std::forward(L_f), std::forward(l_args), + K_f, eta, theta_0, ops, + std::forward(train_tuple), + std::forward(pred_tuple), rng, msgs, + std::forward(covar_args)); +} + +} // namespace math +} // namespace stan + +#endif diff --git a/stan/math/prim/fun/Eigen.hpp b/stan/math/prim/fun/Eigen.hpp index 6efb32cbdea..5ba9337e508 100644 --- a/stan/math/prim/fun/Eigen.hpp +++ b/stan/math/prim/fun/Eigen.hpp @@ -24,6 +24,7 @@ #include #include #include +#include namespace Eigen { diff --git a/stan/math/prim/fun/gp_exp_quad_cov.hpp b/stan/math/prim/fun/gp_exp_quad_cov.hpp index 6bdc2a9c14e..438cd8c47c0 100644 --- a/stan/math/prim/fun/gp_exp_quad_cov.hpp +++ b/stan/math/prim/fun/gp_exp_quad_cov.hpp @@ -30,10 +30,10 @@ namespace internal { * @param neg_half_inv_l_sq The half negative inverse of the length scale * @return squared distance */ -template +template inline typename Eigen::Matrix, Eigen::Dynamic, Eigen::Dynamic> -gp_exp_quad_cov(const std::vector &x, const T_sigma &sigma_sq, +gp_exp_quad_cov(const std::vector &x, const T_sigma &sigma_sq, const T_l &neg_half_inv_l_sq) { using std::exp; const size_t x_size = x.size(); @@ -77,11 +77,13 @@ gp_exp_quad_cov(const std::vector &x, const T_sigma &sigma_sq, * @param neg_half_inv_l_sq The half negative inverse of the length scale * @return squared distance */ -template +template inline typename Eigen::Matrix, Eigen::Dynamic, Eigen::Dynamic> -gp_exp_quad_cov(const std::vector &x1, const std::vector &x2, - const T_sigma &sigma_sq, const T_l &neg_half_inv_l_sq) { +gp_exp_quad_cov(const std::vector &x1, + const std::vector &x2, const T_sigma &sigma_sq, + const T_l &neg_half_inv_l_sq) { using std::exp; Eigen::Matrix, Eigen::Dynamic, Eigen::Dynamic> @@ -120,10 +122,10 @@ gp_exp_quad_cov(const std::vector &x1, const std::vector &x2, * @throw std::domain_error if sigma <= 0, l <= 0, or * x is nan or infinite */ -template +template inline typename Eigen::Matrix, Eigen::Dynamic, Eigen::Dynamic> -gp_exp_quad_cov(const std::vector &x, const T_sigma &sigma, +gp_exp_quad_cov(const std::vector &x, const T_sigma &sigma, const T_l &length_scale) { check_positive("gp_exp_quad_cov", "magnitude", sigma); check_positive("gp_exp_quad_cov", "length scale", length_scale); @@ -160,11 +162,13 @@ gp_exp_quad_cov(const std::vector &x, const T_sigma &sigma, * @throw std::domain_error if sigma <= 0, l <= 0, or * x is nan or infinite */ -template +template inline typename Eigen::Matrix, Eigen::Dynamic, Eigen::Dynamic> -gp_exp_quad_cov(const std::vector> &x, - const T_sigma &sigma, const std::vector &length_scale) { +gp_exp_quad_cov(const std::vector, T_x_alloc> &x, + const T_sigma &sigma, + const std::vector &length_scale) { check_positive_finite("gp_exp_quad_cov", "magnitude", sigma); check_positive_finite("gp_exp_quad_cov", "length scale", length_scale); @@ -203,11 +207,13 @@ gp_exp_quad_cov(const std::vector> &x, * @throw std::domain_error if sigma <= 0, l <= 0, or * x is nan or infinite */ -template +template inline typename Eigen::Matrix, Eigen::Dynamic, Eigen::Dynamic> -gp_exp_quad_cov(const std::vector &x1, const std::vector &x2, - const T_sigma &sigma, const T_l &length_scale) { +gp_exp_quad_cov(const std::vector &x1, + const std::vector &x2, const T_sigma &sigma, + const T_l &length_scale) { const char *function_name = "gp_exp_quad_cov"; check_positive(function_name, "magnitude", sigma); check_positive(function_name, "length scale", length_scale); @@ -252,12 +258,14 @@ gp_exp_quad_cov(const std::vector &x1, const std::vector &x2, * @throw std::domain_error if sigma <= 0, l <= 0, or * x is nan or infinite */ -template +template inline typename Eigen::Matrix, Eigen::Dynamic, Eigen::Dynamic> -gp_exp_quad_cov(const std::vector> &x1, - const std::vector> &x2, - const T_s &sigma, const std::vector &length_scale) { +gp_exp_quad_cov(const std::vector, T_x1_alloc> &x1, + const std::vector, T_x2_alloc> &x2, + const T_s &sigma, + const std::vector &length_scale) { size_t x1_size = x1.size(); size_t x2_size = x2.size(); size_t l_size = length_scale.size(); diff --git a/stan/math/prim/fun/value_of.hpp b/stan/math/prim/fun/value_of.hpp index 64286bbe0c3..6554ae942d0 100644 --- a/stan/math/prim/fun/value_of.hpp +++ b/stan/math/prim/fun/value_of.hpp @@ -1,8 +1,9 @@ #ifndef STAN_MATH_PRIM_FUN_VALUE_OF_HPP #define STAN_MATH_PRIM_FUN_VALUE_OF_HPP -#include #include +#include +#include #include #include @@ -99,6 +100,15 @@ inline auto value_of(EigMat&& M) { return std::forward(M); } +template * = nullptr> +inline auto value_of(Tuple&& tup) { + return stan::math::apply( + [](auto&&... args) { + return std::make_tuple(value_of(std::forward(args))...); + }, + std::forward(tup)); +} + } // namespace math } // namespace stan diff --git a/stan/math/prim/functor.hpp b/stan/math/prim/functor.hpp index 0a6fd0299a6..8164a738b1e 100644 --- a/stan/math/prim/functor.hpp +++ b/stan/math/prim/functor.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include diff --git a/stan/math/prim/functor/coupled_ode_system.hpp b/stan/math/prim/functor/coupled_ode_system.hpp index 5298b53a570..c88802d2ce9 100644 --- a/stan/math/prim/functor/coupled_ode_system.hpp +++ b/stan/math/prim/functor/coupled_ode_system.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include diff --git a/stan/math/prim/functor/filter_map.hpp b/stan/math/prim/functor/filter_map.hpp new file mode 100644 index 00000000000..d38b5a2336f --- /dev/null +++ b/stan/math/prim/functor/filter_map.hpp @@ -0,0 +1,78 @@ +#ifndef STAN_MATH_PRIM_FUNCTOR_FILTER_MAP_HPP +#define STAN_MATH_PRIM_FUNCTOR_FILTER_MAP_HPP + +#include +#include +#include +#include +#include + +namespace stan { +namespace math { +namespace internal { +template