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

Improved rhat diagnostic #3266

Merged
merged 36 commits into from
Apr 23, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
88173cc
newrhat
aleksgorica Jan 30, 2024
98450d7
resolved some of pr1
aleksgorica Jan 31, 2024
b09f512
comments deleted
aleksgorica Jan 31, 2024
dcd21f2
Test changed
aleksgorica Feb 1, 2024
c20ce61
chains_test modified
aleksgorica Feb 4, 2024
0e8379f
Merge commit 'a0154340ce1f195de01839075adad25e10bf28d5' into HEAD
yashikno Feb 6, 2024
5630c51
[Jenkins] auto-formatting by clang-format version 10.0.0-4ubuntu1
stan-buildbot Feb 6, 2024
50617bd
Eigen::Index; index without calculating rows, cols; removed online wa…
aleksgorica Feb 11, 2024
7c5880f
Merge commit 'b6d010fa1dab84d8910b382636c8707c4104fd2e' into HEAD
yashikno Feb 11, 2024
dc130f8
[Jenkins] auto-formatting by clang-format version 10.0.0-4ubuntu1
stan-buildbot Feb 11, 2024
3433d9c
duplicated functions and test for rank version of compute_potential_s…
aleksgorica Feb 26, 2024
4b58653
Merge branch 'newrhat' of https://github.com/aleksgorica/stan into ne…
aleksgorica Feb 26, 2024
3aeab3f
Merge commit '41fd137d7cf89db794210142d6c61f07cf9f0b0a' into HEAD
yashikno Feb 26, 2024
30333ce
[Jenkins] auto-formatting by clang-format version 10.0.0-4ubuntu1
stan-buildbot Feb 26, 2024
77d875f
test chains_test changed
aleksgorica Feb 26, 2024
6221540
Merge branch 'newrhat' of https://github.com/aleksgorica/stan into ne…
aleksgorica Feb 26, 2024
da10f8f
[Jenkins] auto-formatting by clang-format version 10.0.0-4ubuntu1
stan-buildbot Feb 26, 2024
c3b1101
added test values from arviz
aleksgorica Feb 28, 2024
263ac22
Merge branch 'newrhat' of https://github.com/aleksgorica/stan into ne…
aleksgorica Feb 28, 2024
b6ef4bb
Merge commit '348716b22e624b98000a6ee4a4389603da861493' into HEAD
yashikno Feb 28, 2024
51ad447
[Jenkins] auto-formatting by clang-format version 10.0.0-4ubuntu1
stan-buildbot Feb 28, 2024
71bfbcf
smaller changes for comments in pull request
aleksgorica Mar 25, 2024
c371c21
Merge commit '951ce92cee114881c9baa556bb63cebffb9f7772' into HEAD
yashikno Mar 25, 2024
385c80b
[Jenkins] auto-formatting by clang-format version 10.0.0-4ubuntu1
stan-buildbot Mar 25, 2024
51a2504
returning pair for rank rhat
aleksgorica Mar 26, 2024
ba2b6f5
[Jenkins] auto-formatting by clang-format version 10.0.0-4ubuntu1
stan-buildbot Mar 27, 2024
981eedc
small fixes, removed comments in tests
aleksgorica Apr 7, 2024
de941ba
Merge commit '73d9b01d2851d6dd98f1e43dcb56735c20da6cef' into HEAD
yashikno Apr 7, 2024
971aed7
[Jenkins] auto-formatting by clang-format version 10.0.0-4ubuntu1
stan-buildbot Apr 7, 2024
61c4c6c
reverting nonrank functions
aleksgorica Apr 17, 2024
9dae7a0
Merge commit 'c582de8f5a59e721df0c2786830a8c8b921e2961' into HEAD
yashikno Apr 17, 2024
51db135
[Jenkins] auto-formatting by clang-format version 10.0.0-4ubuntu1
stan-buildbot Apr 17, 2024
f12f259
update so scale_reduction calls scale_reduction_rank
SteveBronder Apr 19, 2024
3cb8e97
Merge commit '634034deb3abd6314d980c1aab083f64269f4019' into HEAD
yashikno Apr 19, 2024
da5bb8d
[Jenkins] auto-formatting by clang-format version 10.0.0-4ubuntu1
stan-buildbot Apr 19, 2024
b3631be
Revert "update so scale_reduction calls scale_reduction_rank"
SteveBronder Apr 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
257 changes: 234 additions & 23 deletions src/stan/analyze/mcmc/compute_potential_scale_reduction.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <boost/accumulators/statistics/stats.hpp>
#include <boost/accumulators/statistics/mean.hpp>
#include <boost/accumulators/statistics/variance.hpp>
#include <boost/math/distributions/normal.hpp>
#include <algorithm>
#include <cmath>
#include <vector>
Expand All @@ -16,6 +17,150 @@
namespace stan {
namespace analyze {

/**
* Computes normalized average ranks for draws. Transforming them to normal
* scores using inverse normal transformation and a fractional offset. Based on
* paper https://arxiv.org/abs/1903.08008
* @param draws stores chains in columns
* @return normal scores for average ranks of draws
*
*/

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

Eigen::MatrixXd rank_transform(const Eigen::MatrixXd& draws) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the error on jenkins. This makes it so that there are not multiple definitions for different translation units

Suggested change
Eigen::MatrixXd rank_transform(const Eigen::MatrixXd& draws) {
inline Eigen::MatrixXd rank_transform(const Eigen::MatrixXd& draws) {

const Eigen::Index rows = draws.rows();
const Eigen::Index cols = draws.cols();
const Eigen::Index size = rows * cols;

std::vector<std::pair<double, int>> value_with_index(size);

for (Eigen::Index i = 0; i < size; ++i) {
value_with_index[i] = {draws(i), i};
}

std::sort(value_with_index.begin(), value_with_index.end());

Eigen::MatrixXd rankMatrix = Eigen::MatrixXd::Zero(rows, cols);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use CamelCase for template parameters and snake_case for objects

Suggested change
Eigen::MatrixXd rankMatrix = Eigen::MatrixXd::Zero(rows, cols);
Eigen::MatrixXd rank_matrix = Eigen::MatrixXd::Zero(rows, cols);

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check your code as this should apply everywhere like sumRanks etc


// Assigning average ranks
for (Eigen::Index i = 0; i < size; ++i) {
// Handle ties by averaging ranks
Eigen::Index j = i + 1;
double sumRanks = j;
Eigen::Index count = 1;

while (j < size && value_with_index[j].first == value_with_index[i].first) {
sumRanks += j + 1; // Rank starts from 1
++j;
++count;
}
double avgRank = sumRanks / count;
boost::math::normal_distribution<double> dist;
for (std::size_t k = i; k < j; ++k) {
Eigen::Index index = value_with_index[k].second;
double p = (avgRank - 3.0 / 8.0) / (size - 2.0 * 3.0 / 8.0 + 1.0);
rankMatrix(index) = boost::math::quantile(dist, p);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Initialize things close to where they are used
  2. For integer types we use for indexing or looping stop conditions we usually make them const.
Suggested change
Eigen::Index index = value_with_index[k].second;
double p = (avgRank - 3.0 / 8.0) / (size - 2.0 * 3.0 / 8.0 + 1.0);
rankMatrix(index) = boost::math::quantile(dist, p);
double p = (avgRank - 3.0 / 8.0) / (size - 2.0 * 3.0 / 8.0 + 1.0);
const Eigen::Index index = value_with_index[k].second;
rankMatrix(index) = boost::math::quantile(dist, p);

}
i = j - 1; // Skip over tied elements
}
return rankMatrix;
}

/**
* Computes square root of marginal posterior variance of the estimand by the
* weigted average of within-chain variance W and between-chain variance B.
*
* @param draws stores chains in columns
* @return square root of ((N-1)/N)W + B/N
*
*/

inline double rhat(const Eigen::MatrixXd& draws) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think chains is the better name here since it's chains of draws

Suggested change
inline double rhat(const Eigen::MatrixXd& draws) {
inline double rhat(const Eigen::MatrixXd& chains) {

const Eigen::Index num_chains = draws.cols();
const Eigen::Index num_draws = draws.rows();

Eigen::VectorXd chain_mean(num_chains);
chain_mean = draws.colwise().mean();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Eigen::VectorXd chain_mean(num_chains);
chain_mean = draws.colwise().mean();
Eigen::VectorXd within_chain_mean = draws.colwise().mean();

double total_mean = chain_mean.mean();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
double total_mean = chain_mean.mean();
double across_chain_mean = chain_mean.mean();

double var_between = num_draws
* (chain_mean.array() - total_mean).square().sum()
/ (num_chains - 1);
double var_sum = 0;
for (Eigen::Index col = 0; col < num_chains; ++col) {
var_sum += (draws.col(col).array() - chain_mean(col)).square().sum()
/ (num_draws - 1);
}
double var_within = var_sum / num_chains;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A little more advanced but we can also do this like the below. I added comments to explain the Eigen parts but delete them if you change this.

Also in the below I assume you change chain_mean to be Eigen::RowVectorXd`

Suggested change
double var_sum = 0;
for (Eigen::Index col = 0; col < num_chains; ++col) {
var_sum += (draws.col(col).array() - chain_mean(col)).square().sum()
/ (num_draws - 1);
}
double var_within = var_sum / num_chains;
double within_variance =
// Divide each row by chains and get sum of squares for each chain (getting a vector back)
((chains.array().rowwise() - chain_means.array())
.square()
.colwise()
.sum() /
// divide each sum of square by num_draws, sum the sum of squares, and divide by num chains
(num_draws - 1.0))
.sum() / num_chains;

return sqrt((var_between / var_within + num_draws - 1) / num_draws);
}

/**
* Computes the potential scale reduction (Rhat) using rank based diagnostic for
* the specified parameter across all kept samples. Based on paper
* https://arxiv.org/abs/1903.08008
*
* Current implementation assumes draws are stored in contiguous
* blocks of memory. Chains are trimmed from the back to match the
* length of the shortest chain.
*
* @param draws stores pointers to arrays of chains
* @param sizes stores sizes of chains
* @return potential scale reduction for the specified parameter
*/
inline double compute_potential_scale_reduction_rank(
std::vector<const double*> draws, std::vector<size_t> sizes) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rename these to be a little more clear. begin() is a function in standard library containers that means "an iterator pointing to the first element of a container" so using begin in the name here will signal to people "these pointers are the pointers to the first element of each chain"

Suggested change
std::vector<const double*> draws, std::vector<size_t> sizes) {
std::vector<const double*> chain_begins, std::vector<size_t> chain_sizes) {

int num_chains = sizes.size();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

std::vector's size type is std::size_t

Suggested change
int num_chains = sizes.size();
std::size_t num_chains = sizes.size();

or use auto

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is only the first size checked to see if it is zero and then we return a NaN? If one chain failed we should still be able to use information from all of the other chains. Looking at the rest of the code, unless there is a math reason to not ignore zero sized chains I think we should just prune them

std::vector<const double*> nonzero_chains_begins;
std::vector<std::size_t> nonzero_chain_sizes;
for (int i = 0; i < chain_sizes.size(); ++i) {
  if (!chain_sizes[i]) {
    nonzero_chains_begin.push_back(chain_begins[i]);
    nonzero_chains_sizes.push_back(chain_sizes[i]);
  }
}
if (!nonzero_chains_sizes.size()) {
  return std::numeric_limits<double>::quiet_NaN();
}

size_t num_draws = sizes[0];
if (num_draws == 0) {
return std::numeric_limits<double>::quiet_NaN();
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also should num_draws be min_num_draws since it's the minimum number of draws received from each chain?

for (int chain = 1; chain < num_chains; ++chain) {
num_draws = std::min(num_draws, sizes[chain]);
}

// check if chains are constant; all equal to first draw's value
bool are_all_const = false;
Eigen::VectorXd init_draw = Eigen::VectorXd::Zero(num_chains);

for (int chain = 0; chain < num_chains; chain++) {
Eigen::Map<const Eigen::Matrix<double, Eigen::Dynamic, 1>> draw(
draws[chain], sizes[chain]);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Eigen::Map<const Eigen::Matrix<double, Eigen::Dynamic, 1>> draw(
draws[chain], sizes[chain]);
Eigen::Map<const Eigen::Matrix<double, Eigen::Dynamic, 1>> draws(
nonzero_chain_begins[chain], nonzero_chain_sizes[chain]);


for (int n = 0; n < num_draws; n++) {
if (!std::isfinite(draw(n))) {
return std::numeric_limits<double>::quiet_NaN();
}
aleksgorica marked this conversation as resolved.
Show resolved Hide resolved
}

init_draw(chain) = draw(0);

if (draw.isApproxToConstant(draw(0))) {
are_all_const |= true;
}
}

if (are_all_const) {
// If all chains are constant then return NaN
// if they all equal the same constant value
if (init_draw.isApproxToConstant(init_draw(0))) {
return std::numeric_limits<double>::quiet_NaN();
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But it is fine if each chain is constant, but each one is a different value? tbc I'm asking because idk if that is how the paper is written or not. I suppose this makes sense in the case of many short chains

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you are correct. The current implementation fails if different chains are constant. For example, C1: [1, 1, 1]; C2: [2, 2, 2] would have a within-variance of 0, and the rhat function would return inf due to division by zero. I think the best way to correct this is to check if there exists a non-constant chain.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doing something intelligent around constant chains would be a big improvement on our current NaN behavior. But I'm not sure what that is as there's not a number that makes sense as the ESS.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If all chains have the same constant value, we can't make the difference between all chains being stuck or variable actually being constant (e.g. diagonal of correlation matrix) as Stan doesn't tag the variables. In that case diagnostics in R return NA. If the chains have different constant values, then the variable can't be a true constant, and Rhat Inf is fine.


Eigen::MatrixXd matrix(num_draws, num_chains);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Eigen::MatrixXd matrix(num_draws, num_chains);
Eigen::MatrixXd draws_matrix(num_draws, num_chains);


for (int col = 0; col < num_chains; ++col) {
for (int row = 0; row < num_draws; ++row) {
matrix(row, col) = draws[col][row];
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See other comment about moving this up to the other loop

Suggested change
for (int col = 0; col < num_chains; ++col) {
for (int row = 0; row < num_draws; ++row) {
matrix(row, col) = draws[col][row];
}
}


double rhat_bulk = rhat(rank_transform(matrix));
double rhat_tail = rhat(rank_transform(
(matrix.array() - math::quantile(matrix.reshaped(), 0.5)).abs()));

return std::max(rhat_bulk, rhat_tail);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question for @avehtari

Do we want to just return the max or should we return a pair so the user can see the bulk and tail rhats?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is useful for the user to know both. A diagnostic message could be simplified by reporting if the max of these is too low, but otherwise I would prefer that both would be available for the user. Making them both available does change the io via csv and changing csv structures need to be considered carefully

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay then @aleksgorica can you have this return back an std::pair?

}

/**
* Computes the potential scale reduction (Rhat) for the specified
* parameter across all kept samples.
Expand All @@ -31,6 +176,7 @@ namespace analyze {
* @param sizes stores sizes of chains
* @return potential scale reduction for the specified parameter
*/

inline double compute_potential_scale_reduction(
SteveBronder marked this conversation as resolved.
Show resolved Hide resolved
std::vector<const double*> draws, std::vector<size_t> sizes) {
int num_chains = sizes.size();
Expand Down Expand Up @@ -71,34 +217,39 @@ inline double compute_potential_scale_reduction(
}
}

using boost::accumulators::accumulator_set;
using boost::accumulators::stats;
using boost::accumulators::tag::mean;
using boost::accumulators::tag::variance;
Eigen::MatrixXd matrix(num_draws, num_chains);

Eigen::VectorXd chain_mean(num_chains);
accumulator_set<double, stats<variance>> acc_chain_mean;
Eigen::VectorXd chain_var(num_chains);
double unbiased_var_scale = num_draws / (num_draws - 1.0);

for (int chain = 0; chain < num_chains; ++chain) {
accumulator_set<double, stats<mean, variance>> acc_draw;
for (int n = 0; n < num_draws; ++n) {
acc_draw(draws[chain][n]);
for (int col = 0; col < num_chains; ++col) {
for (int row = 0; row < num_draws; ++row) {
matrix(row, col) = draws[col][row];
}

chain_mean(chain) = boost::accumulators::mean(acc_draw);
acc_chain_mean(chain_mean(chain));
chain_var(chain)
= boost::accumulators::variance(acc_draw) * unbiased_var_scale;
}

double var_between = num_draws * boost::accumulators::variance(acc_chain_mean)
* num_chains / (num_chains - 1);
double var_within = chain_var.mean();
return rhat(matrix);
}

// rewrote [(n-1)*W/n + B/n]/W as (n-1+ B/W)/n
return sqrt((var_between / var_within + num_draws - 1) / num_draws);
/**
* Computes the potential scale reduction (Rhat) using rank based diagnostic for
* the specified parameter across all kept samples. Based on paper
* https://arxiv.org/abs/1903.08008
*
* See more details in Stan reference manual section "Potential
* Scale Reduction". http://mc-stan.org/users/documentation
*
* Current implementation assumes draws are stored in contiguous
* blocks of memory. Chains are trimmed from the back to match the
* length of the shortest chain. Argument size will be broadcast to
* same length as draws.
*
* @param draws stores pointers to arrays of chains
* @param size stores sizes of chains
* @return potential scale reduction for the specified parameter
*/
inline double compute_potential_scale_reduction_rank(
std::vector<const double*> draws, size_t size) {
int num_chains = draws.size();
std::vector<size_t> sizes(num_chains, size);
return compute_potential_scale_reduction_rank(draws, sizes);
}

/**
Expand All @@ -124,6 +275,40 @@ inline double compute_potential_scale_reduction(
return compute_potential_scale_reduction(draws, sizes);
}

/**
* Computes the potential scale reduction (Rhat) using rank based diagnostic for
* the specified parameter across all kept samples. Based on paper
* https://arxiv.org/abs/1903.08008
*
* When the number of total draws N is odd, the (N+1)/2th draw is ignored.
*
* See more details in Stan reference manual section "Potential
* Scale Reduction". http://mc-stan.org/users/documentation
*
* Current implementation assumes draws are stored in contiguous
* blocks of memory. Chains are trimmed from the back to match the
* length of the shortest chain.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You use split_chains which also assumes each chain is the same length

*
* @param draws stores pointers to arrays of chains
* @param sizes stores sizes of chains
* @return potential scale reduction for the specified parameter
*/
inline double compute_split_potential_scale_reduction_rank(
std::vector<const double*> draws, std::vector<size_t> sizes) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want these arguments to come in as constant references. As written this will make a hard copy of the input vectors when you call this function. Making the arguments references (&) means the function will just use the already existing object without making a copy and const means the arguments will be constant in the function (i.e. we will not modify them)

Suggested change
inline double compute_split_potential_scale_reduction_rank(
std::vector<const double*> draws, std::vector<size_t> sizes) {
inline double compute_split_potential_scale_reduction_rank(
const std::vector<const double*>& draws, const std::vector<size_t>& sizes) {

We want containers (like std::vector or Eigen::MatrixXd or std::map) to be passed by constant reference. Small types such as double, int, std::size_t etc. can be passed by value as making copies of them is trivial. Happy to explain this more if you like but don't want to overload you with info. A nice place to read about things like this is Scott Meyers "Effective Modern C++" which if you google should be easy to find a free copy of online.

This comment applies to all the function signatures you added here

int num_chains = sizes.size();
size_t num_draws = sizes[0];
for (int chain = 1; chain < num_chains; ++chain) {
num_draws = std::min(num_draws, sizes[chain]);
}

std::vector<const double*> split_draws = split_chains(draws, sizes);

double half = num_draws / 2.0;
std::vector<size_t> half_sizes(2 * num_chains, std::floor(half));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to make it more clear you are using floating point division and then taking the floor to get the index

Suggested change
double half = num_draws / 2.0;
std::vector<size_t> half_sizes(2 * num_chains, std::floor(half));
std::size_thalf = std::floor(num_draws / 2.0);
std::vector<size_t> half_sizes(2 * num_chains, half);


return compute_potential_scale_reduction_rank(split_draws, half_sizes);
}

/**
* Computes the split potential scale reduction (Rhat) for the
* specified parameter across all kept samples. When the number of
Expand Down Expand Up @@ -156,6 +341,32 @@ inline double compute_split_potential_scale_reduction(
return compute_potential_scale_reduction(split_draws, half_sizes);
}

/**
* Computes the potential scale reduction (Rhat) using rank based diagnostic for
* the specified parameter across all kept samples. Based on paper
* https://arxiv.org/abs/1903.08008
*
* When the number of total draws N is odd, the (N+1)/2th draw is ignored.
*
* See more details in Stan reference manual section "Potential
* Scale Reduction". http://mc-stan.org/users/documentation
*
* Current implementation assumes draws are stored in contiguous
* blocks of memory. Chains are trimmed from the back to match the
* length of the shortest chain. Argument size will be broadcast to
* same length as draws.
*
* @param draws stores pointers to arrays of chains
* @param size stores sizes of chains
* @return potential scale reduction for the specified parameter
*/
inline double compute_split_potential_scale_reduction_rank(
std::vector<const double*> draws, size_t size) {
int num_chains = draws.size();
std::vector<size_t> sizes(num_chains, size);
return compute_split_potential_scale_reduction_rank(draws, sizes);
}

/**
* Computes the split potential scale reduction (Rhat) for the
* specified parameter across all kept samples. When the number of
Expand Down
19 changes: 19 additions & 0 deletions src/stan/mcmc/chains.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,21 @@ class chains {
return split_effective_sample_size(index(name));
}

double split_potential_scale_reduction_rank(const int index) const {
int n_chains = num_chains();
std::vector<const double*> draws(n_chains);
std::vector<size_t> sizes(n_chains);
int n_kept_samples = 0;
for (int chain = 0; chain < n_chains; ++chain) {
n_kept_samples = num_kept_samples(chain);
draws[chain]
= samples_(chain).col(index).bottomRows(n_kept_samples).data();
sizes[chain] = n_kept_samples;
}

return analyze::compute_split_potential_scale_reduction_rank(draws, sizes);
}

double split_potential_scale_reduction(const int index) const {
int n_chains = num_chains();
std::vector<const double*> draws(n_chains);
Expand All @@ -610,6 +625,10 @@ class chains {
return analyze::compute_split_potential_scale_reduction(draws, sizes);
}

double split_potential_scale_reduction_rank(const std::string& name) const {
return split_potential_scale_reduction_rank(index(name));
}

double split_potential_scale_reduction(const std::string& name) const {
return split_potential_scale_reduction(index(name));
}
Expand Down
Loading
Loading