MUQ  0.4.3
Diagnostics.cpp
Go to the documentation of this file.
2 
6 
7 #include <stan/math/fwd/scal.hpp>
8 #include <algorithm>
9 #include <vector>
10 
11 using namespace muq::SamplingAlgorithms;
12 
13 template<typename EstimatorType>
14 Eigen::VectorXd muq::SamplingAlgorithms::Diagnostics::Rhat(std::vector<std::shared_ptr<EstimatorType>> const& collections,
15  boost::property_tree::ptree options)
16 {
17  std::vector<std::shared_ptr<SampleEstimator>> chains;
18 
19  // Check to see if we can cast the estimator to SampleCollection
20  auto samplePtr = std::dynamic_pointer_cast<SampleCollection>(collections.at(0));
21  if(samplePtr){
22 
23  // Create a new vector of sample collections
24  std::vector<std::shared_ptr<SampleCollection>> newCollections(collections.size());
25  for(unsigned int i=0; i<collections.size(); ++i)
26  newCollections.at(i) = std::dynamic_pointer_cast<SampleCollection>(collections.at(i));
27 
28  // Split and possibly transform the chains
29  if(options.get("Split",true))
30  newCollections = SplitChains(newCollections);
31 
32  if(options.get("Transform",false))
33  newCollections = TransformChains(newCollections);
34 
35  // Now cast the split and ranked sample collections back to a SampleEstimator
36  chains.resize(newCollections.size());
37  for(unsigned int chainInd=0; chainInd<newCollections.size(); ++chainInd)
38  chains.at(chainInd) = std::dynamic_pointer_cast<SampleEstimator>(newCollections.at(chainInd));
39 
40  }else{
41 
42  chains.resize(collections.size());
43  for(unsigned int chainInd=0; chainInd<collections.size(); ++chainInd)
44  chains.at(chainInd) = std::dynamic_pointer_cast<SampleEstimator>(collections.at(chainInd));
45 
46  }
47 
48  if(options.get("Multivariate",false)){
49  return BasicMPSRF(chains) * Eigen::VectorXd::Ones(1);
50  }else{
51  return BasicRhat(chains);
52  }
53 }
54 
55 template Eigen::VectorXd muq::SamplingAlgorithms::Diagnostics::Rhat(std::vector<std::shared_ptr<MarkovChain>> const& collections,
56  boost::property_tree::ptree options);
57 template Eigen::VectorXd muq::SamplingAlgorithms::Diagnostics::Rhat(std::vector<std::shared_ptr<MultiIndexEstimator>> const& collections,
58  boost::property_tree::ptree options);
59 template Eigen::VectorXd muq::SamplingAlgorithms::Diagnostics::Rhat(std::vector<std::shared_ptr<SampleCollection>> const& collections,
60  boost::property_tree::ptree options);
61 
62 
63 std::vector<std::shared_ptr<SampleCollection>> muq::SamplingAlgorithms::Diagnostics::SplitChains(std::vector<std::shared_ptr<SampleCollection>> const& origChains,
64  unsigned int numSegments)
65 {
66  std::vector<std::shared_ptr<const SampleCollection>> constChains;
67  for(int i=0; i<origChains.size(); ++i)
68  constChains.push_back(std::const_pointer_cast<const SampleCollection>(origChains.at(i)));
69 
70  return SplitChains(constChains,numSegments);
71 }
73 std::vector<std::shared_ptr<SampleCollection>> muq::SamplingAlgorithms::Diagnostics::SplitChains(std::vector<std::shared_ptr<const SampleCollection>> const& origChains,
74  unsigned int numSegments)
75 {
76  std::vector<std::shared_ptr<SampleCollection>> chains;
77 
78  double fraction = 1.0/double(numSegments);
79 
80  // Figure out how long the split chains will be
81  unsigned int chainLength = std::floor(fraction*origChains.at(0)->size());
82  unsigned int numChains = numSegments*origChains.size();
83  const unsigned int dim = origChains.at(0)->at(0)->TotalDim();
84 
85  chains.resize(numChains);
86 
87  // Extract the split chains
88  for(int i=0; i<origChains.size();++i){
89  for(int j=0; j<numSegments; ++j){
90  chains.at(numSegments*i+j) = origChains.at(i)->segment(j*chainLength, chainLength);
91  }
92  }
93 
94  return chains;
95 }
96 
98 std::vector<std::shared_ptr<SampleCollection>> muq::SamplingAlgorithms::Diagnostics::TransformChains(std::vector<std::shared_ptr<SampleCollection>> const& origChains)
99 {
100  const unsigned int dim = origChains.at(0)->at(0)->TotalDim();
101 
102  unsigned int numChains = origChains.size();
103  unsigned int chainLength = origChains.at(0)->size();
104  const unsigned int totalSamps = numChains*chainLength;
105 
106  std::vector<std::shared_ptr<SampleCollection>> chains;
107  chains.insert(chains.begin(), origChains.begin(), origChains.end());
108 
109  for(unsigned int i=0; i<dim; ++i){
110 
111  // Compute the ranks
112  std::vector<Eigen::VectorXd> ranks = ComputeRanks(chains,i);
113 
114  // Apply a normal transformation to the ranks and compute chain means and variances. See eqn. (14) in https://arxiv.org/pdf/1903.08008.pdf
115  for(unsigned int chainInd=0; chainInd<ranks.size(); ++chainInd){
116  ranks.at(chainInd) = ( (ranks.at(chainInd).array()+0.625)/(totalSamps + 0.25) ).unaryExpr([](double v){return stan::math::inv_Phi(v);});
117 
118  for(unsigned int sampInd=0; sampInd<chains.at(chainInd)->size(); ++sampInd)
119  chains.at(chainInd)->at(sampInd)->StateValue(i) = ranks.at(chainInd)(sampInd);
120  }
121  }
122 
123  return chains;
124 }
125 
126 
127 double muq::SamplingAlgorithms::Diagnostics::BasicMPSRF(std::vector<std::shared_ptr<SampleEstimator>> const& chains)
128 {
129  const unsigned int numChains = chains.size();
130  const unsigned int dim = chains.at(0)->BlockSize(-1);
131 
132  double lengthScale = 1.0;
133 
134  // Check to see if the sample estimator is a SampleCollection, which has a length
135  auto cast = std::dynamic_pointer_cast<SampleCollection>(chains.at(0));
136  if(cast)
137  lengthScale = (cast->size() - 1.0)/cast->size();
138 
139  // A matrix of the chain means.
140  Eigen::MatrixXd chainMeans(dim,numChains);
141 
142  // All of the within-chain covariance matrices
143  std::vector<Eigen::MatrixXd> chainCovs(numChains);
144 
145  // Compute the mean and covariance of each chain
146  for(int i=0; i<numChains; ++i){
147  chainMeans.col(i) = chains.at(i)->Mean();
148  chainCovs.at(i) = chains.at(i)->Covariance();
149  }
150 
151  // Now we're good to compute the MPSRF estimator of Brooks 1998
152  Eigen::VectorXd globalMean = chainMeans.rowwise().mean();
153 
154  Eigen::MatrixXd W = Eigen::MatrixXd::Zero(chainCovs.at(0).rows(), chainCovs.at(0).cols());
155  for(auto& cov : chainCovs)
156  W += cov;
157 
158  W /= chainCovs.size();
159 
160  // Compute the between-chain covariance
161  Eigen::MatrixXd diff = chainMeans.colwise()-globalMean;
162  Eigen::MatrixXd B = (1.0/(numChains-1)) * diff * diff.transpose();
163 
164  Eigen::MatrixXd Vhat = lengthScale * W + (1.0 + 1.0/numChains)*B;
165 
166  Eigen::VectorXd lambdas = Eigen::GeneralizedSelfAdjointEigenSolver<Eigen::MatrixXd>(B,W,Eigen::EigenvaluesOnly).eigenvalues();
167 
168  if(lambdas.size()==1)
169  return std::sqrt( lengthScale + lambdas(0)*(numChains+1)/numChains );
170 
171  if(lambdas(1)>lambdas(0)){
172  // Get an estimate of the marginal posterior variance
173  return std::sqrt( lengthScale + lambdas(lambdas.size()-1)*(numChains+1)/numChains );
174  }else{
175  return std::sqrt( lengthScale + lambdas(0)*(numChains+1)/numChains );
176  }
177 }
178 
179 Eigen::VectorXd muq::SamplingAlgorithms::Diagnostics::BasicRhat(std::vector<std::shared_ptr<SampleEstimator>> const& chains)
180 {
181 
182 
183  // If we aren't working with SampleCollections, use the vanilla Rhat statistic of Gelman 2013
184  const unsigned int numChains = chains.size();
185  const unsigned int dim = chains.at(0)->BlockSize(-1);
186 
187  Eigen::MatrixXd mus(dim, numChains);
188  Eigen::MatrixXd sbjs(dim, numChains);
189  Eigen::MatrixXd vars(dim,numChains);
190 
191  for(unsigned int i=0; i<numChains; ++i){
192  mus.col(i) = chains.at(i)->Mean();
193  sbjs.col(i) = chains.at(i)->CentralMoment(2,mus.col(i)); // Possibly biased estimate
194  vars.col(i) = chains.at(i)->Variance(mus.col(i)); // Possibly unbiased estimate
195  }
196 
197  Eigen::VectorXd mumu = mus.rowwise().mean();
198  Eigen::VectorXd muVar = (mus.colwise()-mumu).array().square().rowwise().sum() / (numChains-1.0);
199 
200  Eigen::VectorXd varEst = sbjs.rowwise().mean() + muVar;
201  Eigen::VectorXd W = vars.rowwise().mean();
202 
203  return (varEst.array() / W.array()).array().sqrt();
204 }
205 
206 
207 
208 std::vector<Eigen::VectorXd> muq::SamplingAlgorithms::Diagnostics::ComputeRanks(std::vector<std::shared_ptr<SampleCollection>> const& collections,
209  unsigned int dim)
210 {
211 
212  // A vector of sample indices [chainInd, sampInd]
213  std::vector<std::pair<unsigned int, unsigned int>> sampInds;
214 
215  for(unsigned int chainInd=0; chainInd<collections.size(); ++chainInd){
216  for(unsigned int sampInd=0; sampInd<collections.at(chainInd)->size(); ++sampInd)
217  sampInds.push_back(std::make_pair(chainInd,sampInd));
218  }
219 
220  // Sort the vector of indices according to the value of the parameters
221  auto compLambda = [&collections, dim](std::pair<unsigned int, unsigned int> const& p1, std::pair<unsigned int, unsigned int> const& p2) {
222  return collections.at(p1.first)->at(p1.second)->StateValue(dim) < collections.at(p2.first)->at(p2.second)->StateValue(dim);
223  };
224 
225  std::stable_sort(sampInds.begin(), sampInds.end(), compLambda);
226 
227  // Set up empty vectors for storing the ranks
228  std::vector<Eigen::VectorXd> ranks(collections.size());
229  for(unsigned int i=0; i<ranks.size(); ++i)
230  ranks.at(i).resize(collections.at(i)->size());
231 
232  // Figure out the rank of each sample
233  unsigned int rawRank = 0;
234  double currVal, nextVal;
235  unsigned int numRepeat, chainInd, sampInd;
236 
237  while(rawRank < sampInds.size()){
238  std::tie(chainInd, sampInd) = sampInds.at(rawRank);
239  currVal = collections.at(chainInd)->at(sampInd)->StateValue(dim);
240 
241  // Look ahead and find the next sample with a new value
242  numRepeat = 1;
243  for(numRepeat=1; numRepeat<sampInds.size()-rawRank; ++numRepeat){
244  std::tie(chainInd, sampInd) = sampInds.at(rawRank+numRepeat);
245  nextVal = collections.at(chainInd)->at(sampInd)->StateValue(dim);
246 
247  if(std::abs(currVal-nextVal)>1e-15){
248  break;
249  }
250  }
251 
252  // Compute the average rank across all of the duplicates
253  double avgRank = 0.5*(rawRank + rawRank+numRepeat-1);
254 
255  // Set the ranks to the average value
256  for(int i=rawRank; i<rawRank+numRepeat; ++i){
257  std::tie(chainInd, sampInd) = sampInds.at(i);
258  ranks.at(chainInd)(sampInd) = avgRank;
259  }
260 
261  // Update how many samples we've moved through
262  rawRank += numRepeat;
263  }
264 
265  return ranks;
266 }
int int diyfp diyfp v
Definition: json.h:15163
@ array
array (ordered collection of values)