Skip to content

Commit

Permalink
Merge pull request #2984 from stan-dev/fix-inv-wishart-cholesky-rng
Browse files Browse the repository at this point in the history
Fix inv wishart cholesky rng
  • Loading branch information
andrjohns authored Dec 11, 2023
2 parents ffeedb5 + 42f7a80 commit 0c16b89
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 8 deletions.
18 changes: 13 additions & 5 deletions stan/math/prim/prob/inv_wishart_cholesky_rng.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@

#include <stan/math/prim/meta.hpp>
#include <stan/math/prim/err.hpp>
#include <stan/math/prim/fun/mdivide_left_tri.hpp>
#include <stan/math/prim/prob/wishart_cholesky_rng.hpp>
#include <stan/math/prim/prob/wishart_rng.hpp>
#include <stan/math/prim/fun/mdivide_left_tri_low.hpp>

namespace stan {
namespace math {
Expand All @@ -16,6 +14,9 @@ namespace math {
* from the inverse Wishart distribution with the specified degrees of freedom
* using the specified random number generator.
*
* Axen, Seth D. "Efficiently generating inverse-Wishart matrices and their
* Cholesky factors." arXiv preprint arXiv:2310.15884 (2023).
*
* @tparam RNG Random number generator type
* @param[in] nu scalar degrees of freedom
* @param[in] L_S lower Cholesky factor of the scale matrix
Expand All @@ -38,8 +39,15 @@ inline Eigen::MatrixXd inv_wishart_cholesky_rng(double nu,
check_positive(function, "Cholesky Scale matrix", L_S.diagonal());
check_positive(function, "columns of Cholesky Scale matrix", L_S.cols());

MatrixXd L_Sinv = mdivide_left_tri<Eigen::Lower>(L_S);
return mdivide_left_tri<Eigen::Lower>(wishart_cholesky_rng(nu, L_Sinv, rng));
MatrixXd B = MatrixXd::Zero(k, k);
for (int j = 0; j < k; ++j) {
for (int i = 0; i < j; ++i) {
B(j, i) = normal_rng(0, 1, rng);
}
B(j, j) = std::sqrt(chi_square_rng(nu - k + j + 1, rng));
}

return mdivide_left_tri_low(B, L_S);
}

} // namespace math
Expand Down
39 changes: 36 additions & 3 deletions test/unit/math/prim/prob/inv_wishart_cholesky_rng_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,15 @@ TEST(ProbDistributionsInvWishartCholesky, SpecialRNGTest) {
using stan::math::inv_wishart_cholesky_rng;
using stan::math::multiply_lower_tri_self_transpose;

boost::random::mt19937 rng(1234U);
boost::random::mt19937 rng(92343U);
int N = 1e5;
double tol = 0.1;
for (int k = 1; k < 5; k++) {
MatrixXd sigma = MatrixXd::Identity(k, k);
MatrixXd L = MatrixXd::Identity(k, k);
MatrixXd Z = MatrixXd::Zero(k, k);
for (int i = 0; i < N; i++) {
Z += stan::math::crossprod(inv_wishart_cholesky_rng(k + 2, sigma, rng));
Z += multiply_lower_tri_self_transpose(
inv_wishart_cholesky_rng(k + 2, L, rng));
}
Z /= N;
for (int j = 0; j < k; j++) {
Expand All @@ -111,3 +112,35 @@ TEST(ProbDistributionsInvWishartCholesky, SpecialRNGTest) {
}
}
}

TEST(ProbDistributionsInvWishartCholesky, compareToInvWishart) {
// Compare the marginal mean

using Eigen::MatrixXd;
using Eigen::VectorXd;
using stan::math::inv_wishart_cholesky_rng;
using stan::math::inv_wishart_rng;
using stan::math::multiply_lower_tri_self_transpose;
using stan::math::qr_thin_Q;

boost::random::mt19937 rng(92343U);
int N = 1e5;
double tol = 0.05;
for (int k = 1; k < 5; k++) {
MatrixXd L = qr_thin_Q(MatrixXd::Random(k, k)).transpose();
L.diagonal() = stan::math::abs(L.diagonal());
MatrixXd sigma = multiply_lower_tri_self_transpose(L);
MatrixXd Z_mean = sigma / (k + 3);
MatrixXd Z_est = MatrixXd::Zero(k, k);
for (int i = 0; i < N; i++) {
Z_est += multiply_lower_tri_self_transpose(
inv_wishart_cholesky_rng(k + 4, L, rng));
}
Z_est /= N;
for (int j = 0; j < k; j++) {
for (int i = 0; i < j; i++) {
EXPECT_NEAR(Z_est(i, j), Z_mean(i, j), tol);
}
}
}
}

0 comments on commit 0c16b89

Please sign in to comment.