hops
EffectiveSampleSize.hpp
Go to the documentation of this file.
1 #ifndef HOPS_EFFECTIVESAMPLESIZE_HPP
2 #define HOPS_EFFECTIVESAMPLESIZE_HPP
3 
4 #include <vector>
5 #include <iostream>
6 #include <memory>
7 
8 #include "Autocorrelation.hpp"
9 
10 namespace hops {
11  template <typename StateType>
12  double computeEffectiveSampleSize (const std::vector<const std::vector<StateType>*>& chains,
13  unsigned long dimension) {
14  unsigned long d = dimension;
15  unsigned long numChains = chains.size();
16  unsigned long numDraws = chains[0]->size();
17 
18  double interChainExpectation = 0;
19  double betweenChainVariance = 0;
20  double withinChainVariance = 0;
21  double varianceEstimate = 0;
22 
23  std::vector<double> rhoHat;
24  std::vector<double> intraChainExpectations = std::vector<double>(numChains, 0);
25  std::vector<double> sampleVariances = std::vector<double>(numChains, 0);
26 
27  std::vector<Eigen::VectorXd> autocorrelations(numChains);
28 
29  for (size_t m = 0; m < numChains; ++m) {
30  for (size_t n = 0; n < numDraws; ++n) {
31  intraChainExpectations[m] += (*chains[m])[n](d);
32  }
33  interChainExpectation += intraChainExpectations[m];
34  intraChainExpectations[m] /= numDraws;
35  }
36 
37  interChainExpectation /= numChains * numDraws;
38 
39  for (size_t m = 0; m < numChains; ++m) {
40  betweenChainVariance += std::pow(intraChainExpectations[m] - interChainExpectation, 2);
41  for (size_t n = 0; n < numDraws; ++n) {
42  sampleVariances[m] += std::pow((*chains[m])[n](d) - intraChainExpectations[m], 2);
43  }
44  withinChainVariance += sampleVariances[m];
45  sampleVariances[m] /= numDraws - 1;
46  }
47 
48  // between-chain variance is not defined for single chains
49  if (numChains > 1) {
50  betweenChainVariance *= numDraws;
51  betweenChainVariance /= numChains - 1;
52  } else {
53  betweenChainVariance = 0;
54  }
55  withinChainVariance /= (numDraws - 1) * numChains;
56  varianceEstimate = ((numDraws-1) * withinChainVariance + betweenChainVariance) / numDraws;
57 
58  for (size_t m = 0; m < numChains; ++m) {
59  computeAutocorrelations(chains[m], autocorrelations[m], d);
60  }
61 
62  double rhoHatEven, rhoHatOdd, autocovariance;
63  for (size_t t = 0; t < numDraws / 2 ; ++t) {
64  autocovariance = 0;
65  for (size_t m = 0; m < numChains; ++m) {
66  autocovariance += (numDraws - 1) * sampleVariances[m] * autocorrelations[m](2*t);
67  }
68  autocovariance /= numChains * numDraws;
69 
70  //std::cout << "1 - (" << withinChainVariance << " - " << autocovariance << ") / " << varianceEstimate << std::endl;
71 
72  // according to stan implementation, set first rhohat to 1.
73  rhoHatEven = (t == 0 ? 1 : 1 - (withinChainVariance - autocovariance) / varianceEstimate);
74 
75  autocovariance = 0;
76  for (size_t m = 0; m < numChains; ++m) {
77  autocovariance += (numDraws - 1) * sampleVariances[m] * autocorrelations[m](2*t + 1);
78  }
79  autocovariance /= numChains * numDraws;
80 
81  //std::cout << "1 - (" << withinChainVariance << " - " << autocovariance << ") / " << varianceEstimate << std::endl;
82 
83  rhoHatOdd = 1 - (withinChainVariance - autocovariance) / varianceEstimate;
84 
85  // break if sequence becomes negative
86  if (rhoHatEven + rhoHatOdd <= 0) {
87  break;
88  }
89 
90  rhoHat.push_back(rhoHatEven);
91  rhoHat.push_back(rhoHatOdd);
92  }
93 
94  if (rhoHatEven > 0) {
95  rhoHat.push_back(rhoHatEven);
96  }
97 
98  //for (auto& rho : rhoHat) {
99  // std::cout << rho << std::endl;
100  //}
101 
102  double tauHat = -1;
103 
104  // turn initial positive sequence to initial monotone sequence, as in stan implementation.
105  // TODO: find detailed explanation of how to do this in some paper.
106  for (size_t t = 1; t < (rhoHat.size() - 2) / 2; ++t) {
107  if (rhoHat[2*t] + rhoHat[2*t+1] > rhoHat[2*t-2] + rhoHat[2*t-1]) {
108  rhoHat[2*t] = (rhoHat[2*t-2] + rhoHat[2*t-1]) / 2;
109  rhoHat[2*t+1] = rhoHat[2*t];
110  }
111  }
112 
113  for (size_t t = 0; t < rhoHat.size() / 2; ++t) {
114  tauHat += 2 * (rhoHat[2*t] + rhoHat[2*t + 1]);
115  }
116 
117  // we can only have odd number of rhoHats, if we added the antiethic case improvement
118  if (rhoHat.size() % 2) {
119  tauHat += rhoHat.back();
120  }
121 
122  //std::cout << "tauHat=" << tauHat << std::endl;
123  //std::cout << "N*M/tauHat=" << numDraws * numChains / tauHat << std::endl;
124 
125  // according to Vehtari et al. 2020
126  return std::min(numDraws * numChains / tauHat, numDraws * numChains * std::log10(numDraws * numChains));
127  }
128 
129  template <typename StateType>
130  std::vector<double> computeEffectiveSampleSize (const std::vector<const std::vector<StateType>*>& chains) {
131  std::vector<double> ess;
132  for (long d = 0; d < (*chains[0])[0].size(); ++d) {
133  ess.push_back(computeEffectiveSampleSize(chains, d));
134  }
135 
136  return ess;
137  }
138 
139  template <typename StateType>
140  double computeEffectiveSampleSize (const std::vector<std::vector<StateType>>& chains, unsigned long dimension) {
141  std::vector<const std::vector<StateType>*> chainsPtrArray;
142  for (auto& chain : chains) {
143  chainsPtrArray.push_back(&chain);
144  }
145  return computeEffectiveSampleSize<StateType>(chainsPtrArray, dimension);
146  }
147 
148  template <typename StateType>
149  std::vector<double> computeEffectiveSampleSize (const std::vector<std::vector<StateType>>& chains) {
150  std::vector<const std::vector<StateType>*> chainsPtrArray;
151  for (auto& chain : chains) {
152  chainsPtrArray.push_back(&chain);
153  }
154  return computeEffectiveSampleSize<StateType>(chainsPtrArray);
155  }
156 }
157 
158 #endif // HOPS_EFFECTIVESAMPLESIZE_HPP
hops::computeAutocorrelations
void computeAutocorrelations(const std::vector< StateType > &draws, Eigen::VectorXd &autocorrelations, unsigned long dimension)
Definition: Autocorrelation.hpp:32
hops
Definition: CsvReader.hpp:8
hops::computeEffectiveSampleSize
double computeEffectiveSampleSize(const std::vector< const std::vector< StateType > * > &chains, unsigned long dimension)
Definition: EffectiveSampleSize.hpp:12
Autocorrelation.hpp