Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/3299 chainset #3313

Merged
merged 58 commits into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
f627e7f
Merge branch 'develop' of https://github.com/stan-dev/stan into develop
mitzimorris Oct 11, 2024
7946efe
adding chainset and unit tests
mitzimorris Oct 11, 2024
35bdb07
basic_ess needed for mcse
mitzimorris Oct 11, 2024
12811f2
[Jenkins] auto-formatting by clang-format version 10.0.0-4ubuntu1
stan-buildbot Oct 11, 2024
7b4843f
added ess_basic, unit tests
mitzimorris Oct 11, 2024
4b65534
Merge branch 'feature/3299-chainset' of https://github.com/stan-dev/s…
mitzimorris Oct 11, 2024
da1b6c5
[Jenkins] auto-formatting by clang-format version 10.0.0-4ubuntu1
stan-buildbot Oct 11, 2024
6cba5df
mcse, mcse_sd match posterior implementation
mitzimorris Oct 12, 2024
4b4b8b0
Merge branch 'feature/3299-chainset' of https://github.com/stan-dev/s…
mitzimorris Oct 12, 2024
345fcea
[Jenkins] auto-formatting by clang-format version 10.0.0-4ubuntu1
stan-buildbot Oct 12, 2024
427cf70
Merge branch 'feature/3299-chainset' of https://github.com/stan-dev/s…
mitzimorris Oct 15, 2024
79c675a
changes per code review
mitzimorris Oct 16, 2024
ead22cc
[Jenkins] auto-formatting by clang-format version 10.0.0-4ubuntu1
stan-buildbot Oct 16, 2024
3534106
changes per code review
mitzimorris Oct 16, 2024
e38ddfc
Merge branch 'feature/3299-chainset' of https://github.com/stan-dev/s…
mitzimorris Oct 16, 2024
210ac05
[Jenkins] auto-formatting by clang-format version 10.0.0-4ubuntu1
stan-buildbot Oct 16, 2024
ada8407
ess use stan::analyze::autocovariance, not stan::math
mitzimorris Oct 17, 2024
2a21f04
Merge branch 'feature/3299-chainset' of https://github.com/stan-dev/s…
mitzimorris Oct 17, 2024
9fed191
[Jenkins] auto-formatting by clang-format version 10.0.0-4ubuntu1
stan-buildbot Oct 17, 2024
65f1c57
update rhat test
mitzimorris Oct 18, 2024
c55f787
Merge branch 'feature/3299-chainset' of https://github.com/stan-dev/s…
mitzimorris Oct 18, 2024
c5e8934
[Jenkins] auto-formatting by clang-format version 10.0.0-4ubuntu1
stan-buildbot Oct 18, 2024
1fa7429
Merge branch 'feature/3299-chainset' of https://github.com/stan-dev/s…
mitzimorris Oct 18, 2024
3981d4c
checkpointing
mitzimorris Oct 18, 2024
d29e5f9
[Jenkins] auto-formatting by clang-format version 10.0.0-4ubuntu1
stan-buildbot Oct 18, 2024
8d0bd5b
Merge branch 'feature/3299-chainset' of https://github.com/stan-dev/s…
mitzimorris Oct 19, 2024
99e8f71
refactored analyze/mcmc fns and unit tests
mitzimorris Oct 19, 2024
5448680
[Jenkins] auto-formatting by clang-format version 10.0.0-4ubuntu1
stan-buildbot Oct 19, 2024
368a7a2
refactored analyze/mcmc fns and unit tests
mitzimorris Oct 19, 2024
a808e64
Merge branch 'feature/3299-chainset' of https://github.com/stan-dev/s…
mitzimorris Oct 19, 2024
f268f54
chainset test cleanup
mitzimorris Oct 20, 2024
5e1739c
[Jenkins] auto-formatting by clang-format version 10.0.0-4ubuntu1
stan-buildbot Oct 20, 2024
1eda263
chainset test all functions
mitzimorris Oct 20, 2024
544f1be
Merge branch 'feature/3299-chainset' of https://github.com/stan-dev/s…
mitzimorris Oct 20, 2024
b47a261
[Jenkins] auto-formatting by clang-format version 10.0.0-4ubuntu1
stan-buildbot Oct 20, 2024
cb1349d
cleanup
mitzimorris Oct 20, 2024
fb0cc65
Merge branch 'feature/3299-chainset' of https://github.com/stan-dev/s…
mitzimorris Oct 20, 2024
aaa7325
[Jenkins] auto-formatting by clang-format version 10.0.0-4ubuntu1
stan-buildbot Oct 20, 2024
2f67bc0
Merge branch 'feature/3299-chainset' of https://github.com/stan-dev/s…
mitzimorris Oct 20, 2024
5a90da7
minor edit
mitzimorris Oct 20, 2024
c38b15f
changes per code review
mitzimorris Oct 21, 2024
199155d
[Jenkins] auto-formatting by clang-format version 10.0.0-4ubuntu1
stan-buildbot Oct 21, 2024
32b7fec
Merge branch 'feature/3299-chainset' of https://github.com/stan-dev/s…
mitzimorris Oct 21, 2024
b4febe8
changes per code review
mitzimorris Oct 21, 2024
b4bca53
[Jenkins] auto-formatting by clang-format version 10.0.0-4ubuntu1
stan-buildbot Oct 21, 2024
608217c
Merge branch 'feature/3299-chainset' of https://github.com/stan-dev/s…
mitzimorris Oct 22, 2024
bf0d581
increase test precision, fix split chain logic
mitzimorris Oct 22, 2024
c411907
[Jenkins] auto-formatting by clang-format version 10.0.0-4ubuntu1
stan-buildbot Oct 22, 2024
17009f8
more unit tests
mitzimorris Oct 22, 2024
2950a00
Merge branch 'feature/3299-chainset' of https://github.com/stan-dev/s…
mitzimorris Oct 22, 2024
1195c35
[Jenkins] auto-formatting by clang-format version 10.0.0-4ubuntu1
stan-buildbot Oct 22, 2024
edbc96e
unit tests against cmdstan 2.35.0
mitzimorris Oct 22, 2024
3099088
Merge branch 'feature/3299-chainset' of https://github.com/stan-dev/s…
mitzimorris Oct 22, 2024
c3ae868
[Jenkins] auto-formatting by clang-format version 10.0.0-4ubuntu1
stan-buildbot Oct 22, 2024
1ccb254
Merge branch 'feature/3299-chainset' of https://github.com/stan-dev/s…
mitzimorris Oct 22, 2024
ad0bd35
checkpointing
mitzimorris Oct 22, 2024
2ee4dc1
changes per code review
mitzimorris Oct 22, 2024
68a3cfb
[Jenkins] auto-formatting by clang-format version 10.0.0-4ubuntu1
stan-buildbot Oct 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/stan/analyze/mcmc/compute_effective_sample_size.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
namespace stan {
namespace analyze {
/**
* \deprecated use split_rank_normalized_ess instead
*
* Computes the effective sample size (ESS) for the specified
* parameter across all kept samples. The value returned is the
* minimum of ESS and the number_total_draws *
Expand Down Expand Up @@ -138,6 +140,8 @@ inline double compute_effective_sample_size(std::vector<const double*> draws,
}

/**
* \deprecated use split_rank_normalized_ess instead
*
* Computes the effective sample size (ESS) for the specified
* parameter across all kept samples. The value returned is the
* minimum of ESS and the number_total_draws *
Expand All @@ -164,6 +168,8 @@ inline double compute_effective_sample_size(std::vector<const double*> draws,
}

/**
* \deprecated use split_rank_normalized_ess instead
*
* Computes the split effective sample size (ESS) for the specified
* parameter across all kept samples. The value returned is the
* minimum of ESS and the number_total_draws *
Expand Down Expand Up @@ -199,6 +205,8 @@ inline double compute_split_effective_sample_size(
}

/**
* \deprecated use split_rank_normalized_ess instead
*
* Computes the split effective sample size (ESS) for the specified
* parameter across all kept samples. The value returned is the
* minimum of ESS and the number_total_draws *
Expand Down
8 changes: 8 additions & 0 deletions src/stan/analyze/mcmc/compute_potential_scale_reduction.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ namespace stan {
namespace analyze {

/**
* \deprecated use `rhat` instead
*
* Computes the potential scale reduction (Rhat) for the specified
* parameter across all kept samples.
*
Expand Down Expand Up @@ -102,6 +104,8 @@ inline double compute_potential_scale_reduction(
}

/**
* \deprecated use split_rank_normalized_rhat instead
*
* Computes the potential scale reduction (Rhat) for the specified
* parameter across all kept samples.
*
Expand All @@ -125,6 +129,8 @@ inline double compute_potential_scale_reduction(
}

/**
* \deprecated use split_rank_normalized_rhat instead
*
* Computes the split potential scale reduction (Rhat) for the
* specified parameter across all kept samples. When the number of
* total draws N is odd, the (N+1)/2th draw is ignored.
Expand Down Expand Up @@ -157,6 +163,8 @@ inline double compute_split_potential_scale_reduction(
}

/**
* \deprecated use split_rank_normalized_rhat instead
*
* Computes the split potential scale reduction (Rhat) for the
* specified parameter across all kept samples. When the number of
* total draws N is odd, the (N+1)/2th draw is ignored.
Expand Down
108 changes: 108 additions & 0 deletions src/stan/analyze/mcmc/ess.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
#ifndef STAN_ANALYZE_MCMC_ESS_HPP
#define STAN_ANALYZE_MCMC_ESS_HPP

#include <stan/math/prim.hpp>
#include <stan/analyze/mcmc/autocovariance.hpp>
#include <algorithm>
#include <cmath>
#include <vector>
#include <limits>

namespace stan {
namespace analyze {

/**
* Computes the effective sample size (ESS) for the specified
* parameter across all chains. The number of draws per chain must be > 3,
mitzimorris marked this conversation as resolved.
Show resolved Hide resolved
* and the values across all draws must be finite and not constant.
* See https://arxiv.org/abs/1903.08008, section 3.2 for discussion.
*
* Sample autocovariance is computed using the implementation in this namespace
* which normalizes lag-k autocorrelation estimators by N instead of (N - k),
* yielding biased but more stable estimators as discussed in Geyer (1992); see
* https://projecteuclid.org/euclid.ss/1177011137.
*
* @param chains matrix of draws across all chains
* @return effective sample size for the specified parameter
*/
double ess(const Eigen::MatrixXd& chains) {
const Eigen::Index num_chains = chains.cols();
const Eigen::Index draws_per_chain = chains.rows();
Eigen::MatrixXd acov(draws_per_chain, num_chains);
Eigen::VectorXd chain_mean(num_chains);
Eigen::VectorXd chain_var(num_chains);

// compute the per-chain autocovariance
for (size_t i = 0; i < num_chains; ++i) {
chain_mean(i) = chains.col(i).mean();
Eigen::Map<const Eigen::VectorXd> draw_col(chains.col(i).data(),
draws_per_chain);
Eigen::VectorXd cov_col(draws_per_chain);
autocovariance<double>(draw_col, cov_col);
acov.col(i) = cov_col;
chain_var(i) = cov_col(0) * draws_per_chain / (draws_per_chain - 1);
}

// compute var_plus, eqn (3)
double w_chain_var = math::mean(chain_var); // W (within chain var)
double var_plus
= w_chain_var * (draws_per_chain - 1) / draws_per_chain; // \hat{var}^{+}
if (num_chains > 1) {
var_plus += math::variance(chain_mean); // B (between chain var)
}

// Geyer's initial positive sequence, eqn (11)
Eigen::VectorXd rho_hat_t = Eigen::VectorXd::Zero(draws_per_chain);
double rho_hat_even = 1.0;
rho_hat_t(0) = rho_hat_even; // lag 0

Eigen::VectorXd acov_t(num_chains);
for (size_t i = 0; i < num_chains; ++i) {
acov_t(i) = acov(1, i);
}
double rho_hat_odd = 1 - (w_chain_var - acov_t.mean()) / var_plus;
rho_hat_t(1) = rho_hat_odd; // lag 1

// compute autocorrelation at lag t for pair (t, t+1)
// paired autocorrelation is guaranteed to be positive, monotone and convex
size_t t = 1;
while (t < draws_per_chain - 4 && (rho_hat_even + rho_hat_odd > 0)
&& !std::isnan(rho_hat_even + rho_hat_odd)) {
for (size_t i = 0; i < num_chains; ++i) {
acov_t(i) = acov.col(i)(t + 1);
}
rho_hat_even = 1 - (w_chain_var - acov_t.mean()) / var_plus;
for (size_t i = 0; i < num_chains; ++i) {
acov_t(i) = acov.col(i)(t + 2);
}
rho_hat_odd = 1 - (w_chain_var - acov_t.mean()) / var_plus;
if ((rho_hat_even + rho_hat_odd) >= 0) {
rho_hat_t(t + 1) = rho_hat_even;
rho_hat_t(t + 2) = rho_hat_odd;
}
// convert initial positive sequence into an initial monotone sequence
if (rho_hat_t(t + 1) + rho_hat_t(t + 2) > rho_hat_t(t - 1) + rho_hat_t(t)) {
rho_hat_t(t + 1) = (rho_hat_t(t - 1) + rho_hat_t(t)) / 2;
rho_hat_t(t + 2) = rho_hat_t(t + 1);
}
t += 2;
}

auto max_t = t; // max lag, used for truncation
// see discussion p. 8, par "In extreme antithetic cases, "
if (rho_hat_even > 0) {
rho_hat_t(max_t + 1) = rho_hat_even;
}

double draws_total = num_chains * draws_per_chain;
// eqn (13): Geyer's truncation rule, w/ modification
double tau_hat = -1 + 2 * rho_hat_t.head(max_t).sum() + rho_hat_t(max_t + 1);
// safety check for negative values and with max ess equal to ess*log10(ess)
tau_hat = std::max(tau_hat, 1 / std::log10(draws_total));
return (draws_total / tau_hat);
}

} // namespace analyze
} // namespace stan

#endif
67 changes: 67 additions & 0 deletions src/stan/analyze/mcmc/mcse.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#ifndef STAN_ANALYZE_MCMC_MCSE_HPP
#define STAN_ANALYZE_MCMC_MCSE_HPP

#include <stan/analyze/mcmc/check_chains.hpp>
#include <stan/analyze/mcmc/split_chains.hpp>
#include <stan/analyze/mcmc/ess.hpp>
#include <stan/math/prim.hpp>
#include <cmath>
#include <limits>
#include <utility>

namespace stan {
namespace analyze {

/**
* Computes the mean Monte Carlo error estimate for the central 90% interval.
* See https://arxiv.org/abs/1903.08008, section 4.4.
* Follows implementation in the R posterior package.
*
* @param chains matrix of draws across all chains
* @return mcse
*/
inline double mcse_mean(const Eigen::MatrixXd& chains) {
const Eigen::Index num_draws = chains.rows();
if (chains.rows() < 4 || !is_finite_and_varies(chains))
return std::numeric_limits<double>::quiet_NaN();

double sample_var
= (chains.array() - chains.mean()).square().sum() / (chains.size() - 1);
return std::sqrt(sample_var / ess(chains));
}

/**
* Computes the standard deviation of the Monte Carlo error estimate
* https://arxiv.org/abs/1903.08008, section 4.4.
* Follows implementation in the R posterior package:
* https://github.com/stan-dev/posterior/blob/98bf52329d68f3307ac4ecaaea659276ee1de8df/R/convergence.R#L478-L496
*
* @param chains matrix of draws across all chains
* @return mcse
*/
inline double mcse_sd(const Eigen::MatrixXd& chains) {
if (chains.rows() < 4 || !is_finite_and_varies(chains))
return std::numeric_limits<double>::quiet_NaN();

// center the data, take abs value
Eigen::MatrixXd draws_ctr = (chains.array() - chains.mean()).abs().matrix();

// posterior pkg fn `ess_mean` computes on split chains
double ess_mean = ess(split_chains(draws_ctr));

// estimated variance (2nd moment)
double Evar = draws_ctr.array().square().mean();

// variance of variance, adjusted for ESS
double fourth_moment = draws_ctr.array().pow(4).mean();
double varvar = (fourth_moment - std::pow(Evar, 2)) / ess_mean;

// variance of standard deviation - use Taylor series approximation
double varsd = varvar / Evar / 4.0;
return std::sqrt(varsd);
}

} // namespace analyze
} // namespace stan

#endif
5 changes: 3 additions & 2 deletions src/stan/analyze/mcmc/rank_normalization.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ namespace stan {
namespace analyze {

/**
* Computes normalized average ranks for pooled draws. Normal scores computed
* using inverse normal transformation and a fractional offset. Based on paper
* Computes normalized average ranks for pooled draws. The values across
* all draws be finite and not constant. Normal scores computed using
* inverse normal transformation and a fractional offset. Based on paper
* https://arxiv.org/abs/1903.08008
*
* @param chains matrix of draws, one column per chain
Expand Down
50 changes: 50 additions & 0 deletions src/stan/analyze/mcmc/rhat.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#ifndef STAN_ANALYZE_MCMC_RHAT_HPP
#define STAN_ANALYZE_MCMC_RHAT_HPP

#include <stan/math/prim.hpp>
#include <algorithm>
#include <cmath>
#include <vector>
#include <limits>

namespace stan {
namespace analyze {

/**
* Computes square root of marginal posterior variance of the estimand by the
* weighted average of within-chain variance W and between-chain variance B.
*
* @param chains stores chains in columns
* @return square root of ((N-1)/N)W + B/N
*/
inline double rhat(const Eigen::MatrixXd& chains) {
const Eigen::Index num_chains = chains.cols();
const Eigen::Index num_draws = chains.rows();

Eigen::RowVectorXd within_chain_means = chains.colwise().mean();
double across_chain_mean = within_chain_means.mean();
double between_variance
= num_draws
* (within_chain_means.array() - across_chain_mean).square().sum()
/ (num_chains - 1);
double within_variance =
// Divide each row by chains and get sum of squares for each chain
// (getting a vector back)
((chains.rowwise() - within_chain_means)
.array()
.square()
.colwise()
// divide each sum of square by num_draws, sum the sum of squares,
// and divide by num chains
.sum()
/ (num_draws - 1.0))
.sum()
/ num_chains;

return sqrt((between_variance / within_variance + num_draws - 1) / num_draws);
}

} // namespace analyze
} // namespace stan

#endif
16 changes: 9 additions & 7 deletions src/stan/analyze/mcmc/split_chains.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,17 @@ namespace analyze {
inline Eigen::MatrixXd split_chains(const std::vector<Eigen::MatrixXd>& chains,
const int index) {
size_t num_chains = chains.size();
size_t num_samples = chains[0].rows();
size_t half = std::floor(num_samples / 2.0);
size_t num_draws = chains[0].rows();
size_t half = std::floor(num_draws / 2.0);
size_t tail_start = std::floor((num_draws + 1) / 2.0);

Eigen::MatrixXd split_draws_matrix(half, num_chains * 2);
int split_i = 0;
for (std::size_t i = 0; i < num_chains; ++i) {
Eigen::Map<const Eigen::VectorXd> head_block(chains[i].col(index).data(),
half);
Eigen::Map<const Eigen::VectorXd> tail_block(
chains[i].col(index).data() + half, half);
chains[i].col(index).data() + tail_start, half);

split_draws_matrix.col(split_i) = head_block;
split_draws_matrix.col(split_i + 1) = tail_block;
Expand All @@ -47,15 +48,16 @@ inline Eigen::MatrixXd split_chains(const std::vector<Eigen::MatrixXd>& chains,
*/
inline Eigen::MatrixXd split_chains(const Eigen::MatrixXd& samples) {
size_t num_chains = samples.cols();
size_t num_samples = samples.rows();
size_t half = std::floor(num_samples / 2.0);
size_t num_draws = samples.rows();
size_t half = std::floor(num_draws / 2.0);
size_t tail_start = std::floor((num_draws + 1) / 2.0);

Eigen::MatrixXd split_draws_matrix(half, num_chains * 2);
int split_i = 0;
for (std::size_t i = 0; i < num_chains; ++i) {
Eigen::Map<const Eigen::VectorXd> head_block(samples.col(i).data(), half);
Eigen::Map<const Eigen::VectorXd> tail_block(samples.col(i).data() + half,
half);
Eigen::Map<const Eigen::VectorXd> tail_block(
samples.col(i).data() + tail_start, half);

split_draws_matrix.col(split_i) = head_block;
split_draws_matrix.col(split_i + 1) = tail_block;
Expand Down
Loading
Loading