1 #include "MUQ/SamplingAlgorithms/SingleChainMCMC.h"
2 
3 #include <chrono>
4 
5 #include "MUQ/Utilities/AnyHelpers.h"
6 #include "MUQ/Utilities/StringUtilities.h"
7 
8 #include "MUQ/SamplingAlgorithms/MarkovChain.h"
9 
10 namespace pt = boost::property_tree;
11 using namespace muq::Utilities;
12 using namespace muq::SamplingAlgorithms;
13 
SingleChainMCMC(pt::ptree pt,std::shared_ptr<AbstractSamplingProblem> const & problem)14 SingleChainMCMC::SingleChainMCMC(pt::ptree pt,
15                                  std::shared_ptr<AbstractSamplingProblem> const& problem) :
16                  SamplingAlgorithm(std::make_shared<MarkovChain>()),
17                  printLevel(pt.get("PrintLevel",3))
18 {
19   Setup(pt, problem);
20 }
21 
22 #if MUQ_HAS_PARCER
SingleChainMCMC(pt::ptree pt,std::shared_ptr<AbstractSamplingProblem> const & problem,std::shared_ptr<parcer::Communicator> const & comm)23 SingleChainMCMC::SingleChainMCMC(pt::ptree pt,
24                                  std::shared_ptr<AbstractSamplingProblem> const& problem,
25                                  std::shared_ptr<parcer::Communicator> const& comm) :
26                  SamplingAlgorithm(std::make_shared<MarkovChain>(), comm),
27                  printLevel(pt.get("PrintLevel",3))
28 {
29   Setup(pt, problem);
30 }
31 
SingleChainMCMC(pt::ptree pt,std::shared_ptr<parcer::Communicator> const & comm,std::vector<std::shared_ptr<TransitionKernel>> const & kernelsIn)32 SingleChainMCMC::SingleChainMCMC(pt::ptree pt,
33                                  std::shared_ptr<parcer::Communicator> const& comm,
34                                  std::vector<std::shared_ptr<TransitionKernel> > const& kernelsIn) :
35                  SamplingAlgorithm(std::make_shared<MarkovChain>(), comm),
36                  printLevel(pt.get("PrintLevel",3))
37 {
38   Setup(pt, kernelsIn);
39 }
40 
41 #endif
42 
SingleChainMCMC(boost::property_tree::ptree pt,std::vector<std::shared_ptr<TransitionKernel>> const & kernelsIn)43 SingleChainMCMC::SingleChainMCMC(boost::property_tree::ptree pt,
44                                  std::vector<std::shared_ptr<TransitionKernel> > const& kernelsIn) :
45                 SamplingAlgorithm(std::make_shared<MarkovChain>()),
46                 printLevel(pt.get("PrintLevel",3))
47 {
48   Setup(pt,kernelsIn);
49 }
50 
Setup(pt::ptree pt,std::vector<std::shared_ptr<TransitionKernel>> const & kernelsIn)51 void SingleChainMCMC::Setup(pt::ptree pt,
52                             std::vector<std::shared_ptr<TransitionKernel>> const& kernelsIn) {
53 
54   numSamps = pt.get<unsigned int>("NumSamples");
55   burnIn = pt.get("BurnIn",0);
56 
57   kernels = kernelsIn;
58 
59   scheduler = std::make_shared<ThinScheduler>(pt);
60   schedulerQOI = std::make_shared<ThinScheduler>(pt);
61   assert(scheduler);
62   assert(schedulerQOI);
63 
64 #if MUQ_HAS_PARCER
65 
66   for(int i=0; i<kernels.size(); ++i)
67     kernels.at(i)->SetCommunicator(comm);
68 
69 #endif
70 
71 }
72 
Setup(pt::ptree pt,std::shared_ptr<AbstractSamplingProblem> const & problem)73 void SingleChainMCMC::Setup(pt::ptree pt, std::shared_ptr<AbstractSamplingProblem> const& problem) {
74 
75   std::string kernelString = pt.get<std::string>("KernelList");
76 
77   std::vector<std::string> kernelNames = StringUtilities::Split(kernelString, ',');
78 
79   std::vector<std::shared_ptr<TransitionKernel>> kernelVec;
80   unsigned int numBlocks = kernelNames.size();
81   kernelVec.resize(numBlocks);
82 
83   // Add the block id to a child tree and construct a kernel for each block
84   for(int i=0; i<numBlocks; ++i) {
85     boost::property_tree::ptree subTree = pt.get_child(kernelNames.at(i));
86     subTree.put("BlockIndex",i);
87 
88     problem->AddOptions(subTree);
89     kernelVec.at(i) = TransitionKernel::Construct(subTree, problem);
90   }
91 
92   Setup(pt, kernelVec);
93 }
94 
PrintStatus(std::string prefix,unsigned int currInd) const95 void SingleChainMCMC::PrintStatus(std::string prefix, unsigned int currInd) const
96 {
97   std::cout << prefix << int(std::floor(double((currInd - 1) * 100) / double(numSamps))) << "% Complete" << std::endl;
98   if(printLevel>1){
99     for(int blockInd=0; blockInd<kernels.size(); ++blockInd){
100       std::cout << prefix << "  Block " << blockInd << ":\n";
101       kernels.at(blockInd)->PrintStatus(prefix + "    ");
102     }
103   }
104 }
105 
RunImpl(std::vector<Eigen::VectorXd> const & x0)106 std::shared_ptr<SampleCollection> SingleChainMCMC::RunImpl(std::vector<Eigen::VectorXd> const& x0) {
107   if( !x0.empty() ) { SetState(x0); }
108 
109   // What is the next iteration that we want to print at
110   const unsigned int printIncr = std::floor(numSamps / double(10));
111   unsigned int nextPrintInd = printIncr;
112 
113   // Run until we've received the desired number of samples
114   if(printLevel>0)
115     std::cout << "Starting single chain MCMC sampler..." << std::endl;
116 
117   while(sampNum < numSamps)
118   {
119     // Should we print
120     if(sampNum > nextPrintInd){
121       if(printLevel>0){
122         PrintStatus("  ", sampNum);
123       }
124       nextPrintInd += printIncr;
125     }
126 
127     Sample();
128   }
129 
130 
131   if(printLevel>0){
132     PrintStatus("  ", numSamps+1);
133     std::cout << "Completed in " << totalTime << " seconds." << std::endl;
134   }
135 
136   return samples;
137 }
138 
Sample()139 void SingleChainMCMC::Sample() {
140   auto startTime = std::chrono::high_resolution_clock::now();
141 
142   std::vector<std::shared_ptr<SamplingState> > newStates;
143 
144   // Loop through each parameter block
145   for(int blockInd=0; blockInd<kernels.size(); ++blockInd){
146     // kernel prestep
147     kernels.at(blockInd)->PreStep(sampNum, prevState);
148 
149     // use the kernel to get the next state(s)
150     newStates = kernels.at(blockInd)->Step(sampNum, prevState);
151 
152     // save when these samples where created
153     const double now = std::chrono::duration<double>(std::chrono::high_resolution_clock::now()-startTime).count();
154     for( auto it=newStates.begin(); it!=newStates.end(); ++it ) { (*it)->meta["time"] = now; }
155 
156     // kernel post-processing
157     kernels.at(blockInd)->PostStep(sampNum, newStates);
158 
159     // add the new states to the SampleCollection (this also increments sampNum)
160     prevState = SaveSamples(newStates, lastSavedState, sampNum);
161   }
162 
163   auto endTime = std::chrono::high_resolution_clock::now();
164   totalTime += std::chrono::duration<double>(endTime - startTime).count();
165 }
166 
SaveSamples(std::vector<std::shared_ptr<SamplingState>> const & newStates,std::shared_ptr<SamplingState> & lastSavedState,unsigned int & sampNum) const167 std::shared_ptr<SamplingState> SingleChainMCMC::SaveSamples(std::vector<std::shared_ptr<SamplingState> > const& newStates, std::shared_ptr<SamplingState>& lastSavedState, unsigned int& sampNum) const {
168   for( auto it : newStates ) {
169     // save the sample, if we want to
170     if( ShouldSave(sampNum) ) {
171       samples->Add(it);
172       if (it->HasMeta("QOI")) {
173         std::shared_ptr<SamplingState> qoi = AnyCast(it->meta["QOI"]);
174         QOIs->Add(qoi);
175       }
176     }
177 
178     // increment the number of samples and break of we hit the max. number
179     if( ++sampNum>=numSamps ) { return it; }
180   }
181 
182   return newStates.back();
183 }
184 
ShouldSave(unsigned int const sampNum) const185 bool SingleChainMCMC::ShouldSave(unsigned int const sampNum) const { return sampNum>=burnIn && scheduler->ShouldSave(sampNum); }
186 
SetState(std::vector<Eigen::VectorXd> const & x0)187 void SingleChainMCMC::SetState(std::vector<Eigen::VectorXd> const& x0) {
188   prevState = std::make_shared<SamplingState>(x0);
189   samples->Add(prevState);
190 }
191