MaCh3  2.6.1
Reference Guide
ProcessMCMCModule.cpp
Go to the documentation of this file.
1 
4 //MaCh3 includes
5 #include "Fitters/OscProcessor.h"
6 #include "Manager/Manager.h"
8 
9 
10 namespace M3{
11 
13 
15  m_parser = std::make_unique<MaCh3ArgumentParser>("process", "1.0", argparse::default_arguments::help);
16  m_parser->add_description("Main executable responsible for different types of MCMC processing like drawing posteriors, triangle plots etc.");
17  m_parser->add_epilog("""\
18  ProcessMCMC The main application for analysing the ND280 chain.\n\
19  It prints posterior distributions after the burn-in cut and allows comparison of two or three different chains.\n\
20  Several options can be configured directly in the app, such as selection, burn-in cut, and whether to plot xsec+flux or only flux.\n\
21  \n\
22  Additional functionality includes:\n\
23  Produce a covariance matrix with multithreading (RAM intensive due to caching)\n\
24  Violin plots\n\
25  Credible intervals and regions\n\
26  Calculation of Bayes factors with significance based on the Jeffreys scale\n\
27  Triangle plots\n\
28  Study of covariance matrix stability\n""");
29  m_parser->add_argument("--corr")
30  .help("plot correlation - Same as PlotCorr option in config.")
31  .flag();
32  m_parser->add_argument("--MakeCredibleIntervals")
33  .help("Same as MakeCredibleIntervals option in config.")
34  .flag();
35  m_parser->add_argument("--CalcBayesFactor")
36  .help("Same as CalcBayesFactor option in config.")
37  .flag();
38  m_parser->add_argument("--CalcSavageDickey")
39  .help("Same as CalcSavageDickey option in config.")
40  .flag();
41  m_parser->add_argument("--CalcBipolarPlot")
42  .help("Same as CalcBipolarPlot option in config.")
43  .flag();
44  m_parser->add_argument("--CalcParameterEvolution")
45  .help("Same as CalcParameterEvolution option in config.")
46  .flag();
47  m_parser->add_argument("config")
48  .help("Config file.")
49  .metavar("CONFIG")
50  .required();
51  m_parser->add_argument("mcmc-chain")
52  .help("MCMC chain root files and titles.\n"
53  "single chain mode: MCMC_CHAIN1\n"
54  "two chain mode : MCMC_CHAIN1 TITLE1 MCMC_CHAIN2 TITLE2\n"
55  "three chain mode : MCMC_CHAIN1 TITLE1 MCMC_CHAIN2 TITLE2 MCMC_CHAIN3 TITLE3")
56  .metavar("MCMC_CHAIN1 [TITLE1 MCMC_CHAIN2 TITLE2 [MCMC_CHAIN3 TITLE3]]")
57  .nargs(1, 6)
58  .required();
59  return m_parser.get();
60  }
61 
62 
65  nFiles = 0;
66  config = m_parser->get<std::string>("config");
67  auto mcmc_chain_args = m_parser->get<std::vector<std::string>>("mcmc-chain");
68 
69  int nargs = static_cast<int>(mcmc_chain_args.size());
70  if (nargs != 1 && nargs !=4 && nargs != 6)
71  {
72  MACH3LOG_ERROR("invalid number of arguments: {}", nargs);
73  std::cerr << *m_parser;
74  MACH3LOG_ERROR("invalid number of arguments: {}", nargs);
75  throw MaCh3Exception(__FILE__ , __LINE__ );
76  }
77 
78  YAML::Node card_yaml = M3OpenConfig(config);
79  if (!CheckNodeExists(card_yaml, "ProcessMCMC")) {
80  MACH3LOG_ERROR("The 'ProcessMCMC' node is not defined in the YAML configuration.");
81  throw MaCh3Exception(__FILE__ , __LINE__ );
82  }
83 
84  if (mcmc_chain_args.size() == 1)
85  {
86  MACH3LOG_INFO("Producing single fit output");
87  std::string filename = mcmc_chain_args[0];
88  this->ProcessMCMC(filename);
89  }
90  // If we want to compare two or more fits (e.g. binning changes or introducing new params/priors)
91  else if (mcmc_chain_args.size() > 1)
92  {
93  for (std::size_t i = 0; i < mcmc_chain_args.size(); i += 2) {
94  FileNames.push_back(mcmc_chain_args[i]);
95  TitleNames.push_back(mcmc_chain_args[i + 1]);
96  }
97  // MACH3LOG_INFO("Producing two fit comparison");
98  // FileNames.push_back(files[0]);
99  // TitleNames.push_back("ONE"); // todo fix
100 
101  // FileNames.push_back(files[1]);
102  // TitleNames.push_back("TWO");
103  // //KS: If there is third file add it
104  // if(files.size() == 3)
105  // {
106  // FileNames.push_back(files[2]);
107  // TitleNames.push_back("THREE");
108  // }
109 
110  this->MultipleProcessMCMC();
111  }
112  return 0;
113  }
114 
129  std::map<std::string, std::pair<double, double>> ProcessMCMCModule::GetCustomBinning(const YAML::Node& Settings)
130  {
131  std::map<std::string, std::pair<double, double>> CustomBinning;
132  if (Settings["CustomBinEdges"]) {
133  const YAML::Node& edges = Settings["CustomBinEdges"];
134 
135  for (const auto& node : edges) {
136  std::string key = node.first.as<std::string>();
137  auto values = node.second.as<std::vector<double>>();
138 
139  if (values.size() == 2) {
140  CustomBinning[key] = std::make_pair(values[0], values[1]);
141  MACH3LOG_DEBUG("Adding custom binning {} with {:.4f}, {:.4f}", key, values[0], values[1]);
142  } else {
143  MACH3LOG_ERROR("Invalid number of values for key: {}", key);
144  throw MaCh3Exception(__FILE__ , __LINE__ );
145  }
146  }
147  }
148  return CustomBinning;
149  }
150 
158  void ProcessMCMCModule::ProcessMCMC(const std::string& inputFile)
159  {
160  MACH3LOG_INFO("File for study: {} with config {}", inputFile, config);
161  // Make the processor)
162  auto Processor = std::make_unique<OscProcessor>(inputFile);
163 
164  YAML::Node card_yaml = M3OpenConfig(config.c_str());
165  YAML::Node Settings = card_yaml["ProcessMCMC"];
166 
167  const bool PlotCorr = m_parser->get<bool>("--corr") || GetFromManager<bool>(Settings["PlotCorr"], false, __FILE__, __LINE__);
168 
169  Processor->SetExcludedTypes(GetFromManager<std::vector<std::string>>(Settings["ExcludedTypes"], {}, __FILE__, __LINE__));
170  Processor->SetExcludedNames(GetFromManager<std::vector<std::string>>(Settings["ExcludedNames"], {}, __FILE__, __LINE__));
171  Processor->SetExcludedGroups(GetFromManager<std::vector<std::string>>(Settings["ExcludedGroups"], {}, __FILE__, __LINE__));
172 
173  //Apply additional cuts to 1D posterior
174  Processor->SetPosterior1DCut(GetFromManager<std::string>(Settings["Posterior1DCut"], "", __FILE__, __LINE__));
175 
176  if(PlotCorr) Processor->SetOutputSuffix("_drawCorr");
177  //KS:Turn off plotting detector and some other setting, should be via some config
178  Processor->SetPlotRelativeToPrior(GetFromManager<bool>(Settings["PlotRelativeToPrior"], false, __FILE__, __LINE__));
179  Processor->SetPrintToPDF(GetFromManager<bool>(Settings["PrintToPDF"], true, __FILE__, __LINE__));
180 
181  //KS: Whether you want prior error bands for parameters with flat prior or not
182  Processor->SetPlotErrorForFlatPrior(GetFromManager<bool>(Settings["PlotErrorForFlatPrior"], true, __FILE__, __LINE__));
183  Processor->SetFancyNames(GetFromManager<bool>(Settings["FancyNames"], true, __FILE__, __LINE__));
184  Processor->SetPlotBinValue(GetFromManager<bool>(Settings["PlotBinValue"], false, __FILE__, __LINE__));
185  //KS: Plot only 2D posteriors with correlations greater than 0.2
186  Processor->SetPost2DPlotThreshold(GetFromManager<double>(Settings["Post2DPlotThreshold"], 0.2, __FILE__, __LINE__));
187  // Weight to be considered
188  Processor->SetReweightNames(GetFromManager<std::vector<std::string>>(Settings["WeightNames"], {"Weight"}, __FILE__, __LINE__));
189 
190  Processor->Initialise();
191 
192  if(Settings["BurnInSteps"]) {
193  Processor->SetStepCut(Settings["BurnInSteps"].as<int>());
194  } else {
195  MACH3LOG_WARN("BurnInSteps not set, defaulting to 20%");
196  Processor->SetStepCut(static_cast<int>(Processor->GetnSteps()/5));
197  }
198  if(Settings["MaxEntries"]) {
199  Processor->SetEntries(Get<int>(Settings["MaxEntries"], __FILE__, __LINE__));
200  }
201  if(Settings["NBins"]) {
202  Processor->SetNBins(Get<int>(Settings["NBins"], __FILE__, __LINE__));
203  }
204  if(Settings["Thinning"])
205  {
206  if(Settings["Thinning"][0].as<bool>()){
207  Processor->ThinMCMC(Settings["Thinning"][1].as<int>());
208  }
209  }
210  // Make the postfit
211  Processor->MakePostfit(this->GetCustomBinning(Settings));
212  Processor->DrawPostfit();
213  //KS: Should set via config whether you want below or not
214  if(m_parser->get<bool>("--MakeCredibleIntervals") || GetFromManager<bool>(Settings["MakeCredibleIntervals"], true, __FILE__, __LINE__)) {
215  Processor->MakeCredibleIntervals(GetFromManager<std::vector<double>>(Settings["CredibleIntervals"], {0.99, 0.90, 0.68}, __FILE__, __LINE__),
216  GetFromManager<std::vector<short int>>(Settings["CredibleIntervalsColours"], {436, 430, 422}, __FILE__, __LINE__),
217  GetFromManager<bool>(Settings["CredibleInSigmas"], false, __FILE__, __LINE__));
218  }
219  if(m_parser->get<bool>("--CalcBayesFactor") || GetFromManager<bool>(Settings["CalcBayesFactor"], true, __FILE__, __LINE__)) this->CalcBayesFactor(Processor.get());
220  if(m_parser->get<bool>("--CalcSavageDickey") || GetFromManager<bool>(Settings["CalcSavageDickey"], true, __FILE__, __LINE__)) this->CalcSavageDickey(Processor.get());
221  if(m_parser->get<bool>("--CalcBipolarPlot") || GetFromManager<bool>(Settings["CalcBipolarPlot"], false, __FILE__, __LINE__)) this->CalcBipolarPlot(Processor.get());
222  if(m_parser->get<bool>("--CalcParameterEvolution") || GetFromManager<bool>(Settings["CalcParameterEvolution"], false, __FILE__, __LINE__)) this->CalcParameterEvolution(Processor.get());
223 
224  if(PlotCorr)
225  {
226  Processor->SetSmoothing(GetFromManager<bool>(Settings["Smoothing"], true, __FILE__, __LINE__));
227  // Make the covariance matrix
228  //We have different treatment for multithread
229  Processor->CacheSteps();
230  //KS: Since we cached let's make fancy violins :)
231  if(GetFromManager<bool>(Settings["MakeViolin"], true, __FILE__, __LINE__)) Processor->MakeViolin();
232  Processor->MakeCovariance_MP();
233 
234  Processor->DrawCovariance();
235  if(GetFromManager<bool>(Settings["MakeCovarianceYAML"], true, __FILE__, __LINE__)) Processor->MakeCovarianceYAML(GetFromManager<std::string>(Settings["CovarianceYAMLOutName"], "UpdatedCorrelationMatrix.yaml", __FILE__, __LINE__), GetFromManager<std::string>(Settings["CovarianceYAMLMeansMethod"], "HPD", __FILE__, __LINE__));
236 
237  auto const &MakeSubOptimality = Settings["MakeSubOptimality"];
238  if(MakeSubOptimality[0].as<bool>()) Processor->MakeSubOptimality(MakeSubOptimality[1].as<int>());
239 
240  if(GetFromManager<bool>(Settings["MakeCredibleRegions"], false, __FILE__, __LINE__)) {
241  Processor->MakeCredibleRegions(GetFromManager<std::vector<double>>(Settings["CredibleRegions"], {0.99, 0.90, 0.68}, __FILE__, __LINE__),
242  GetFromManager<std::vector<short int>>(Settings["CredibleRegionStyle"], {2, 1, 3}, __FILE__, __LINE__),
243  GetFromManager<std::vector<short int>>(Settings["CredibleRegionColor"], {413, 406, 416}, __FILE__, __LINE__),
244  GetFromManager<bool>(Settings["CredibleInSigmas"], false, __FILE__, __LINE__),
245  GetFromManager<bool>(Settings["Draw2DPosterior"], true, __FILE__, __LINE__),
246  GetFromManager<bool>(Settings["DrawBestFit"], true, __FILE__, __LINE__));
247  }
248  if(GetFromManager<bool>(Settings["GetTrianglePlot"], true, __FILE__, __LINE__)) this->GetTrianglePlot(Processor.get());
249 
250  //KS: When creating covariance matrix longest time is spend on caching every step, since we already cached we can run some fancy covariance stability diagnostic
251  if(GetFromManager<bool>(Settings["DiagnoseCovarianceMatrix"], false, __FILE__, __LINE__)) this->DiagnoseCovarianceMatrix(Processor.get(), inputFile);
252  }
253  Processor->ProduceChi2(GetFromManager<std::string>(Settings["Chi2Group"], "Osc", __FILE__, __LINE__));
254  if(GetFromManager<bool>(Settings["JarlskogAnalysis"], true, __FILE__, __LINE__)) Processor->PerformJarlskogAnalysis();
255  if(GetFromManager<bool>(Settings["ProducePMNSElements"], true, __FILE__, __LINE__)) Processor->ProducePMNSElements();
256  if(GetFromManager<bool>(Settings["ProduceUnitarityTriangles"], true, __FILE__, __LINE__)) Processor->ProduceUnitarityTriangles();
257  if(GetFromManager<bool>(Settings["MakePiePlot"], true, __FILE__, __LINE__)) Processor->MakePiePlot();
258  }
259 
266  {
267  YAML::Node card_yaml = M3OpenConfig(config.c_str());
268  YAML::Node Settings = card_yaml["ProcessMCMC"];
269 
270  constexpr Color_t PosteriorColor[] = {kBlue-1, kRed, kGreen+2};
271  //constexpr Style_t PosteriorStyle[] = {kSolid, kDashed, kDotted};
272  nFiles = int(FileNames.size());
273  std::vector<std::unique_ptr<MCMCProcessor>> Processor(nFiles);
274 
275  if(!Settings["BurnInSteps"]) {
276  MACH3LOG_WARN("BurnInSteps not set, defaulting to 20%");
277  }
278 
279  for (int ik = 0; ik < nFiles; ik++)
280  {
281  MACH3LOG_INFO("File for study: {}", FileNames[ik]);
282  // Make the processor
283  Processor[ik] = std::make_unique<MCMCProcessor>(FileNames[ik]);
284  Processor[ik]->SetOutputSuffix(("_" + std::to_string(ik)).c_str());
285 
286  Processor[ik]->SetExcludedTypes(GetFromManager<std::vector<std::string>>(Settings["ExcludedTypes"], {}, __FILE__, __LINE__));
287  Processor[ik]->SetExcludedNames(GetFromManager<std::vector<std::string>>(Settings["ExcludedNames"], {}, __FILE__, __LINE__));
288  Processor[ik]->SetExcludedGroups(GetFromManager<std::vector<std::string>>(Settings["ExcludedGroups"], {}, __FILE__, __LINE__));
289 
290  //Apply additional cuts to 1D posterior
291  Processor[ik]->SetPosterior1DCut(GetFromManager<std::string>(Settings["Posterior1DCut"], "", __FILE__, __LINE__));
292 
293  Processor[ik]->SetPlotRelativeToPrior(GetFromManager<bool>(Settings["PlotRelativeToPrior"], false, __FILE__, __LINE__));
294  Processor[ik]->SetFancyNames(GetFromManager<bool>(Settings["FancyNames"], true, __FILE__, __LINE__));
295 
296  // Weight to be considered
297  Processor[ik]->SetReweightNames(GetFromManager<std::vector<std::string>>(Settings["WeightNames"], {"Weight"}, __FILE__, __LINE__));
298  Processor[ik]->Initialise();
299 
300  if(Settings["BurnInSteps"]) {
301  Processor[ik]->SetStepCut(Settings["BurnInSteps"].as<int>());
302  }else {
303  Processor[ik]->SetStepCut(static_cast<int>(Processor[ik]->GetnSteps()/5));
304  }
305 
306  if(Settings["MaxEntries"]) {
307  Processor[ik]->SetEntries(Get<int>(Settings["MaxEntries"], __FILE__, __LINE__));
308  }
309  if(Settings["NBins"]) {
310  Processor[ik]->SetNBins(Get<int>(Settings["NBins"], __FILE__, __LINE__));
311  }
312  }
313 
314  Processor[0]->MakePostfit(this->GetCustomBinning(Settings));
315  Processor[0]->DrawPostfit();
316  // Get edges from first histogram to ensure all params use same binning
317  std::map<std::string, std::pair<double, double>> ParamEdges;
318  for(int i = 0; i < Processor[0]->GetNParams(); ++i) {
319  // Get the histogram for the i-th parameter
320  TH1D* hist = Processor[0]->GetHpost(i);
321  if (!hist) {
322  MACH3LOG_DEBUG("Histogram for parameter {} is null.", i);
323  continue;
324  }
325 
326  // Get the parameter name (title of the histogram)
327  std::string paramName = hist->GetTitle();
328 
329  // Get the axis limits (edges)
330  TAxis* axis = hist->GetXaxis();
331  double xmin = axis->GetXmin();
332  double xmax = axis->GetXmax();
333 
334  MACH3LOG_DEBUG("Adding bin edges for {} equal to {:.4f}, {:.4f}",paramName, xmin, xmax);
335  // Insert into the map
336  ParamEdges[paramName] = std::make_pair(xmin, xmax);
337  }
338 
339  //KS: Multithreading here is very tempting but there are some issues with root that need to be resovled :(
340  for (int ik = 1; ik < nFiles; ik++)
341  {
342  // Make the postfit
343  Processor[ik]->MakePostfit(ParamEdges);
344  Processor[ik]->DrawPostfit();
345  }
346 
347  // Open a TCanvas to write the posterior onto
348  auto Posterior = std::make_unique<TCanvas>("PosteriorMulti", "PosteriorMulti", 0, 0, 1024, 1024);
349  gStyle->SetOptStat(0);
350  gStyle->SetOptTitle(0);
351  Posterior->SetGrid();
352  Posterior->SetBottomMargin(0.1f);
353  Posterior->SetTopMargin(0.05f);
354  Posterior->SetRightMargin(0.03f);
355  Posterior->SetLeftMargin(0.15f);
356 
357  // First filename: keep path, just remove ".root"
358  // Would be nice to specify outpath in a later update
359  size_t pos = FileNames[0].rfind(".root");
360  std::string base = (pos == std::string::npos) ? FileNames[0] : FileNames[0].substr(0, pos);
361  TString canvasname = base;
362 
363  // Remaining filenames: strip path and ".root"
364  // So if you have /path/to/file1.root and /path/to/file2.root or /another/path/to/file2.root, canvasname = /path/to/file1_file2.root
365  for (int ik = 1; ik < nFiles; ik++) {
366  pos = FileNames[ik].find_last_of('/');
367  base = (pos == std::string::npos) ? FileNames[ik] : FileNames[ik].substr(pos + 1);
368  pos = base.rfind(".root");
369  if (pos != std::string::npos) base = base.substr(0, pos);
370  canvasname += "_" + TString(base);
371  }
372 
373  canvasname = canvasname +".pdf[";
374 
375  Posterior->Print(canvasname);
376  // Once the pdf file is open no longer need to bracket
377  canvasname.ReplaceAll("[","");
378 
379  for(int i = 0; i < Processor[0]->GetNParams(); ++i)
380  {
381  // This holds the posterior density
382  std::vector<std::unique_ptr<TH1D>> hpost(nFiles);
383  std::vector<std::unique_ptr<TLine>> hpd(nFiles);
384  hpost[0] = M3::Clone(Processor[0]->GetHpost(i));
385  hpost[0]->GetYaxis()->SetTitle("Posterior Density");
386  bool Skip = false;
387  for (int ik = 1 ; ik < nFiles; ik++)
388  {
389  // KS: If somehow this chain doesn't given params we skip it
390  const int Index = Processor[ik]->GetParamIndexFromName(hpost[0]->GetTitle());
391  if(Index == M3::_BAD_INT_)
392  {
393  Skip = true;
394  break;
395  }
396  hpost[ik] = M3::Clone(Processor[ik]->GetHpost(Index));
397  }
398 
399  // Don't plot if this is a fixed histogram (i.e. the peak is the whole integral)
400  if(hpost[0]->GetMaximum() == hpost[0]->Integral()*1.5 || Skip) {
401  continue;
402  }
403  for (int ik = 0; ik < nFiles; ik++)
404  {
405  RemoveFitter(hpost[ik].get(), "Gauss");
406 
407  // Set some nice colours
408  hpost[ik]->SetLineColor(PosteriorColor[ik]);
409  //hpost[ik]->SetLineStyle(PosteriorStyle[ik]);
410  hpost[ik]->SetLineWidth(2);
411 
412  // Area normalise the distributions
413  hpost[ik]->Scale(1./hpost[ik]->Integral());
414  }
415  TString Title;
416  double Prior = 1.0;
417  double PriorError = 1.0;
418 
419  Processor[0]->GetNthParameter(i, Prior, PriorError, Title);
420 
421  // Now make the TLine for the Asimov
422  auto Asimov = std::make_unique<TLine>(Prior, hpost[0]->GetMinimum(), Prior, hpost[0]->GetMaximum());
423  Asimov->SetLineColor(kRed-3);
424  Asimov->SetLineWidth(2);
425  Asimov->SetLineStyle(kDashed);
426 
427  // Make a nice little TLegend
428  auto leg = std::make_unique<TLegend>(0.20, 0.7, 0.6, 0.97);
429  leg->SetTextSize(0.03f);
430  leg->SetFillColor(0);
431  leg->SetFillStyle(0);
432  leg->SetLineColor(0);
433  leg->SetLineStyle(0);
434  TString asimovLeg = Form("#splitline{Prior}{x = %.2f , #sigma = %.2f}", Prior, PriorError);
435  leg->AddEntry(Asimov.get(), asimovLeg, "l");
436 
437  for (int ik = 0; ik < nFiles; ik++)
438  {
439  TString rebinLeg = Form("#splitline{%s}{#mu = %.2f, #sigma = %.2f}", TitleNames[ik].c_str(), hpost[ik]->GetMean(), hpost[ik]->GetRMS());
440  leg->AddEntry(hpost[ik].get(), rebinLeg, "l");
441 
442  hpd[ik] = std::make_unique<TLine>(hpost[ik]->GetBinCenter(hpost[ik]->GetMaximumBin()), hpost[ik]->GetMinimum(),
443  hpost[ik]->GetBinCenter(hpost[ik]->GetMaximumBin()), hpost[ik]->GetMaximum());
444  hpd[ik]->SetLineColor(hpost[ik]->GetLineColor());
445  hpd[ik]->SetLineWidth(2);
446  hpd[ik]->SetLineStyle(kSolid);
447  }
448 
449  // Find the maximum value to nicely resize hist
450  double maximum = 0;
451  for (int ik = 0; ik < nFiles; ik++) maximum = std::max(maximum, hpost[ik]->GetMaximum());
452  for (int ik = 0; ik < nFiles; ik++) hpost[ik]->SetMaximum(1.3*maximum);
453 
454  hpost[0]->Draw("hist");
455  for (int ik = 1; ik < nFiles; ik++) hpost[ik]->Draw("hist same");
456  Asimov->Draw("same");
457  for (int ik = 0; ik < nFiles; ik++) hpd[ik]->Draw("same");
458  leg->Draw("same");
459  Posterior->cd();
460  Posterior->Print(canvasname);
461  }//End loop over parameters
462 
463  // Finally draw the parameter plot onto the PDF
464  // Close the .pdf file with all the posteriors
465  Posterior->cd();
466  Posterior->Clear();
467 
468  if(GetFromManager<bool>(Settings["PerformKStest"], true, __FILE__, __LINE__)) this->KolmogorovSmirnovTest(Processor, Posterior, canvasname);
469 
470  // Close the pdf file
471  MACH3LOG_INFO("Closing pdf {}", canvasname);
472  canvasname+="]";
473  Posterior->Print(canvasname);
474  }
475 
484  {
485  YAML::Node card_yaml = M3OpenConfig(config.c_str());
486  YAML::Node Settings = card_yaml["ProcessMCMC"];
487 
488  std::vector<std::string> ParNames;
489  std::vector<std::vector<double>> Model1Bounds;
490  std::vector<std::vector<double>> Model2Bounds;
491  std::vector<std::vector<std::string>> ModelNames;
492  for (const auto& dg : Settings["BayesFactor"])
493  {
494  ParNames.push_back(dg[0].as<std::string>());
495  ModelNames.push_back(dg[1].as<std::vector<std::string>>());
496  Model1Bounds.push_back(dg[2].as<std::vector<double>>());
497  Model2Bounds.push_back(dg[3].as<std::vector<double>>());
498  }
499 
500  Processor->GetBayesFactor(ParNames, Model1Bounds, Model2Bounds, ModelNames);
501  }
502 
511  {
512  YAML::Node card_yaml = M3OpenConfig(config.c_str());
513  YAML::Node Settings = card_yaml["ProcessMCMC"];
514 
515  std::vector<std::string> ParNames;
516  std::vector<double> EvaluationPoint;
517  std::vector<std::vector<double>> Bounds;
518 
519  for (const auto& d : Settings["SavageDickey"])
520  {
521  ParNames.push_back(d[0].as<std::string>());
522  EvaluationPoint.push_back(d[1].as<double>());
523  Bounds.push_back(d[2].as<std::vector<double>>());
524  }
525  Processor->GetSavageDickey(ParNames, EvaluationPoint, Bounds);
526  }
527 
536  {
537  YAML::Node card_yaml = M3OpenConfig(config.c_str());
538  YAML::Node Settings = card_yaml["ProcessMCMC"];
539 
540  std::vector<std::string> ParNames;
541  std::vector<int> Intervals;
542  for (const auto& d : Settings["ParameterEvolution"])
543  {
544  ParNames.push_back(d[0].as<std::string>());
545  Intervals.push_back(d[1].as<int>());
546  }
547  Processor->ParameterEvolution(ParNames, Intervals);
548  }
549 
558  {
559  YAML::Node card_yaml = M3OpenConfig(config.c_str());
560  YAML::Node Settings = card_yaml["ProcessMCMC"];
561 
562  std::vector<std::string> ParNames;
563  for (const auto& d : Settings["BipolarPlot"])
564  {
565  ParNames.push_back(d[0].as<std::string>());
566  }
567  Processor->GetPolarPlot(ParNames);
568  }
569 
578  YAML::Node card_yaml = M3OpenConfig(config.c_str());
579  YAML::Node Settings = card_yaml["ProcessMCMC"];
580 
581  for (const auto& dg : Settings["TrianglePlot"])
582  {
583  std::string ParName = dg[0].as<std::string>();
584 
585  std::vector<std::string> NameVec = dg[1].as<std::vector<std::string>>();
586  Processor->MakeTrianglePlot(NameVec,
587  GetFromManager<std::vector<double>>(Settings["CredibleIntervals"], {0.99, 0.90, 0.68}, __FILE__, __LINE__),
588  GetFromManager<std::vector<short int>>(Settings["CredibleIntervalsColours"], {436, 430, 422}, __FILE__, __LINE__),
589  GetFromManager<std::vector<double>>(Settings["CredibleRegions"], {0.99, 0.90, 0.68}, __FILE__, __LINE__),
590  GetFromManager<std::vector<short int>>(Settings["CredibleRegionStyle"], {2, 1, 3}, __FILE__, __LINE__),
591  GetFromManager<std::vector<short int>>(Settings["CredibleRegionColor"], {413, 406, 416}, __FILE__, __LINE__),
592  GetFromManager<bool>(Settings["CredibleInSigmas"], false, __FILE__, __LINE__));
593  }
594  }
595 
604  void ProcessMCMCModule::DiagnoseCovarianceMatrix(MCMCProcessor* Processor, const std::string& inputFile)
605  {
606  //Turn of plots from Processor
607  Processor->SetPrintToPDF(false);
608  // Open a TCanvas to write the posterior onto
609  auto Canvas = std::make_unique<TCanvas>("Canvas", "Canvas", 0, 0, 1024, 1024);
610  Canvas->SetGrid();
611  gStyle->SetOptStat(0);
612  gStyle->SetOptTitle(0);
613  Canvas->SetTickx();
614  Canvas->SetTicky();
615  Canvas->SetBottomMargin(0.1f);
616  Canvas->SetTopMargin(0.05f);
617  Canvas->SetRightMargin(0.15f);
618  Canvas->SetLeftMargin(0.10f);
619 
620  //KS: Fancy colours
621  const int NRGBs = 10;
622  TColor::InitializeColors();
623  Double_t stops[NRGBs] = { 0.00, 0.10, 0.25, 0.35, 0.50, 0.60, 0.65, 0.75, 0.90, 1.00 };
624  Double_t red[NRGBs] = { 0.50, 1.00, 1.00, 0.25, 0.00, 0.10, 0.50, 1.00, 0.75, 0.55 };
625  Double_t green[NRGBs] = { 0.00, 0.25, 1.00, 0.25, 0.00, 0.60, 0.90, 1.00, 0.75, 0.75 };
626  Double_t blue[NRGBs] = { 0.00, 0.25, 1.00, 1.00, 0.50, 0.60, 0.90, 1.00, 0.05, 0.05 };
627  TColor::CreateGradientColorTable(NRGBs, stops, red, green, blue, 255);
628  gStyle->SetNumberContours(255);
629 
630  std::string OutName = inputFile;
631  OutName = OutName.substr(0, OutName.find(".root"));
632  Canvas->Print(Form("Correlation_%s.pdf[", OutName.c_str()), "pdf");
633  Canvas->Print(Form("Covariance_%s.pdf[", OutName.c_str()), "pdf");
634 
635  YAML::Node card_yaml = M3OpenConfig(config.c_str());
636  YAML::Node Settings = card_yaml["ProcessMCMC"];
637 
638  const int entries = int(Processor->GetnSteps());
639  const int NIntervals = GetFromManager<int>(Settings["NIntervals"], 5, __FILE__, __LINE__);
640  const int IntervalsSize = entries/NIntervals;
641  //We start with burn from 0 (no burn in at all)
642  int BurnIn = 0;
643  MACH3LOG_INFO("Diagnosing matrices with entries={}, NIntervals={} and IntervalsSize={}", entries, NIntervals, IntervalsSize);
644 
645  TMatrixDSym *Covariance = nullptr;
646  TMatrixDSym *Correlation = nullptr;
647 
648  TH2D *CovariancePreviousHist = nullptr;
649  TH2D *CorrelationPreviousHist = nullptr;
650 
651  TH2D *CovarianceHist = nullptr;
652  TH2D *CorrelationHist = nullptr;
653 
654  //KS: Get first covariances, we need two for comparison...
655  Processor->SetStepCut(BurnIn);
656  Processor->GetCovariance(Covariance, Correlation);
657 
658  CovariancePreviousHist = this->TMatrixIntoTH2D(Covariance, "Covariance");
659  CorrelationPreviousHist = this->TMatrixIntoTH2D(Correlation, "Correlation");
660 
661  delete Covariance;
662  Covariance = nullptr;
663  delete Correlation;
664  Correlation = nullptr;
665 
666  //KS: Loop over all desired cuts
667  for(int k = 1; k < NIntervals; ++k)
668  {
669  BurnIn = k*IntervalsSize;
670  Processor->SetStepCut(BurnIn);
671  Processor->GetCovariance(Covariance, Correlation);
672  Processor->Reset2DPosteriors();
673 
674  CovarianceHist = this->TMatrixIntoTH2D(Covariance, "Covariance");
675  CorrelationHist = this->TMatrixIntoTH2D(Correlation, "Correlation");
676 
677  TH2D *CovarianceDiff = static_cast<TH2D*>(CovarianceHist->Clone("Covariance_Ratio"));
678  TH2D *CorrelationDiff = static_cast<TH2D*>(CorrelationHist->Clone("Correlation_Ratio"));
679 
680  //KS: Bit messy but quite often covariance is 0 is divided by 0 is problematic so
681  #ifdef MULTITHREAD
682  #pragma omp parallel for
683  #endif
684  for (int j = 1; j < CovarianceDiff->GetXaxis()->GetNbins()+1; ++j)
685  {
686  for (int i = 1; i < CovarianceDiff->GetYaxis()->GetNbins()+1; ++i)
687  {
688  if( std::fabs (CovarianceDiff->GetBinContent(j, i)) < 1.e-5 && std::fabs (CovariancePreviousHist->GetBinContent(j, i)) < 1.e-5)
689  {
690  CovarianceDiff->SetBinContent(j, i, M3::_BAD_DOUBLE_);
691  CovariancePreviousHist->SetBinContent(j, i, M3::_BAD_DOUBLE_);
692  }
693  if( std::fabs (CorrelationDiff->GetBinContent(j, i)) < 1.e-5 && std::fabs (CorrelationPreviousHist->GetBinContent(j, i)) < 1.e-5)
694  {
695  CorrelationDiff->SetBinContent(j, i, M3::_BAD_DOUBLE_);
696  CorrelationPreviousHist->SetBinContent(j, i, M3::_BAD_DOUBLE_);
697  }
698  }
699  }
700  //Divide matrices
701  CovarianceDiff->Divide(CovariancePreviousHist);
702  CorrelationDiff->Divide(CorrelationPreviousHist);
703 
704  //Now it is time for fancy names etc.
705  for (int j = 0; j < CovarianceDiff->GetXaxis()->GetNbins(); ++j)
706  {
707  TString Title = "";
708  double Prior = 1.0;
709  double PriorError = 1.0;
710 
711  Processor->GetNthParameter(j, Prior, PriorError, Title);
712 
713  CovarianceDiff->GetXaxis()->SetBinLabel(j+1, Title);
714  CovarianceDiff->GetYaxis()->SetBinLabel(j+1, Title);
715  CorrelationDiff->GetXaxis()->SetBinLabel(j+1, Title);
716  CorrelationDiff->GetYaxis()->SetBinLabel(j+1, Title);
717  }
718  CovarianceDiff->GetXaxis()->SetLabelSize(0.015f);
719  CovarianceDiff->GetYaxis()->SetLabelSize(0.015f);
720  CorrelationDiff->GetXaxis()->SetLabelSize(0.015f);
721  CorrelationDiff->GetYaxis()->SetLabelSize(0.015f);
722 
723  std::stringstream ss;
724  ss << "BCut_";
725  ss << BurnIn;
726  ss << "/";
727  ss << "BCut_";
728  ss << (k-1)*IntervalsSize;
729  std::string str = ss.str();
730 
731  TString Title = "Cov " + str;
732  CovarianceDiff->GetZaxis()->SetTitle( Title );
733  Title = "Corr " + str;
734  CorrelationDiff->GetZaxis()->SetTitle(Title);
735 
736  CovarianceDiff->SetMinimum(-2);
737  CovarianceDiff->SetMaximum(2);
738  CorrelationDiff->SetMinimum(-2);
739  CorrelationDiff->SetMaximum(2);
740 
741  Canvas->cd();
742  CovarianceDiff->Draw("colz");
743  Canvas->Print(Form("Covariance_%s.pdf", OutName.c_str()), "pdf");
744 
745  CorrelationDiff->Draw("colz");
746  Canvas->Print(Form("Correlation_%s.pdf", OutName.c_str()), "pdf");
747 
748  //KS: Current hist become previous as we need it for further comparison
749  delete CovariancePreviousHist;
750  CovariancePreviousHist = static_cast<TH2D*>(CovarianceHist->Clone());
751  delete CorrelationPreviousHist;
752  CorrelationPreviousHist = static_cast<TH2D*>(CorrelationHist->Clone());
753 
754  delete CovarianceHist;
755  CovarianceHist = nullptr;
756  delete CorrelationHist;
757  CorrelationHist = nullptr;
758 
759  delete CovarianceDiff;
760  delete CorrelationDiff;
761  delete Covariance;
762  Covariance = nullptr;
763  delete Correlation;
764  Correlation = nullptr;
765  }
766  Canvas->cd();
767  Canvas->Print(Form("Covariance_%s.pdf]", OutName.c_str()), "pdf");
768  Canvas->Print(Form("Correlation_%s.pdf]", OutName.c_str()), "pdf");
769 
770  Processor->SetPrintToPDF(true);
771  if(Covariance != nullptr) delete Covariance;
772  if(Correlation != nullptr) delete Correlation;
773  if(CovariancePreviousHist != nullptr) delete CovariancePreviousHist;
774  if(CorrelationPreviousHist != nullptr) delete CorrelationPreviousHist;
775  if(CovarianceHist != nullptr) delete CovarianceHist;
776  if(CorrelationHist != nullptr) delete CorrelationHist;
777  }
778 
787  TH2D* ProcessMCMCModule::TMatrixIntoTH2D(TMatrixDSym* Matrix, const std::string& title)
788  {
789  TH2D* hMatrix = new TH2D(title.c_str(), title.c_str(), Matrix->GetNrows(), 0.0, Matrix->GetNrows(), Matrix->GetNcols(), 0.0, Matrix->GetNcols());
790  for(int i = 0; i < Matrix->GetNrows(); i++)
791  {
792  for(int j = 0; j < Matrix->GetNcols(); j++)
793  {
794  //KS: +1 because there is offset in histogram relative to TMatrix
795  hMatrix->SetBinContent(i+1,j+1, (*Matrix)(i,j));
796  }
797  }
798  return hMatrix;
799  }
800 
810  void ProcessMCMCModule::KolmogorovSmirnovTest(const std::vector<std::unique_ptr<MCMCProcessor>>& Processor,
811  const std::unique_ptr<TCanvas>& Posterior,
812  const TString& canvasname)
813  {
814  constexpr Color_t CumulativeColor[] = {kBlue-1, kRed, kGreen+2};
815  constexpr Style_t CumulativeStyle[] = {kSolid, kDashed, kDotted};
816 
817  for(int i = 0; i < Processor[0]->GetNParams(); ++i)
818  {
819  // This holds the posterior density
820  std::vector<std::unique_ptr<TH1D>> hpost(nFiles);
821  std::vector<std::unique_ptr<TH1D>> CumulativeDistribution(nFiles);
822 
823  TString Title;
824  double Prior = 1.0;
825  double PriorError = 1.0;
826 
827  Processor[0]->GetNthParameter(i, Prior, PriorError, Title);
828  bool Skip = false;
829  for (int ik = 0 ; ik < nFiles; ik++)
830  {
831  int Index = 0;
832  if(ik == 0 ) Index = i;
833  else
834  {
835  // KS: If somehow this chain doesn't given params we skip it
836  Index = Processor[ik]->GetParamIndexFromName(hpost[0]->GetTitle());
837  if(Index == M3::_BAD_INT_)
838  {
839  Skip = true;
840  break;
841  }
842  }
843  hpost[ik] = M3::Clone(Processor[ik]->GetHpost(Index));
844  CumulativeDistribution[ik] = M3::Clone(Processor[ik]->GetHpost(Index));
845  CumulativeDistribution[ik]->Fill(0., 0.);
846  CumulativeDistribution[ik]->Reset();
847  CumulativeDistribution[ik]->SetMaximum(1.);
848  TString TempTitle = Title+" Kolmogorov Smirnov";
849  CumulativeDistribution[ik]->SetTitle(TempTitle);
850 
851  TempTitle = Title+" Value";
852  CumulativeDistribution[ik]->GetXaxis()->SetTitle(TempTitle);
853  CumulativeDistribution[ik]->GetYaxis()->SetTitle("Cumulative Probability");
854 
855  CumulativeDistribution[ik]->SetLineWidth(2);
856  CumulativeDistribution[ik]->SetLineColor(CumulativeColor[ik]);
857  CumulativeDistribution[ik]->SetLineStyle(CumulativeStyle[ik]);
858  }
859 
860  // Don't plot if this is a fixed histogram (i.e. the peak is the whole integral)
861  if(hpost[0]->GetMaximum() == hpost[0]->Integral()*1.5 || Skip) {
862  continue;
863  }
864 
865  for (int ik = 0 ; ik < nFiles; ik++)
866  {
867  const int NumberOfBins = hpost[ik]->GetXaxis()->GetNbins();
868  double Cumulative = 0;
869  const double Integral = hpost[ik]->Integral();
870  for (int j = 1; j < NumberOfBins+1; ++j)
871  {
872  Cumulative += hpost[ik]->GetBinContent(j)/Integral;
873  CumulativeDistribution[ik]->SetBinContent(j, Cumulative);
874  }
875  //KS: Set overflow to 1 just in case
876  CumulativeDistribution[ik]->SetBinContent(NumberOfBins+1, 1.);
877  }
878 
879  std::vector<int> TestStatBin(nFiles, 0);
880  std::vector<double> TestStatD(nFiles, -999);
881  std::vector<std::unique_ptr<TLine>> LineD(nFiles);
882  //Find KS statistic
883  for (int ik = 1 ; ik < nFiles; ik++)
884  {
885  const int NumberOfBins = CumulativeDistribution[0]->GetXaxis()->GetNbins();
886  for (int j = 1; j < NumberOfBins+1; ++j)
887  {
888  const double BinValue = CumulativeDistribution[0]->GetBinCenter(j);
889  const int BinNumber = CumulativeDistribution[ik]->FindBin(BinValue);
890  //KS: Calculate D statistic for this bin, only save it if it's bigger than previously found value
891  double TempDstat = std::fabs(CumulativeDistribution[0]->GetBinContent(j) - CumulativeDistribution[ik]->GetBinContent(BinNumber));
892  if(TempDstat > TestStatD[ik])
893  {
894  TestStatD[ik] = TempDstat;
895  TestStatBin[ik] = j;
896  }
897  }
898  }
899 
900  for (int ik = 0 ; ik < nFiles; ik++)
901  {
902  LineD[ik] = std::make_unique<TLine>(CumulativeDistribution[0]->GetBinCenter(TestStatBin[ik]), 0, CumulativeDistribution[0]->GetBinCenter(TestStatBin[ik]), CumulativeDistribution[0]->GetBinContent(TestStatBin[ik]));
903  LineD[ik]->SetLineColor(CumulativeColor[ik]);
904  LineD[ik]->SetLineWidth(2.0);
905  }
906  CumulativeDistribution[0]->Draw();
907  for (int ik = 0 ; ik < nFiles; ik++)
908  CumulativeDistribution[ik]->Draw("SAME");
909 
910  auto leg = std::make_unique<TLegend>(0.15, 0.7, 0.5, 0.90);
911  leg->SetTextSize(0.04f);
912  for (int ik = 0; ik < nFiles; ik++)
913  leg->AddEntry(CumulativeDistribution[ik].get(), TitleNames[ik].c_str(), "l");
914  for (int ik = 1; ik < nFiles; ik++)
915  leg->AddEntry(LineD[ik].get(), Form("#Delta D = %.4f", TestStatD[ik]), "l");
916 
917  leg->SetLineColor(0);
918  leg->SetLineStyle(0);
919  leg->SetFillColor(0);
920  leg->SetFillStyle(0);
921  leg->Draw("SAME");
922 
923  for (int ik = 1; ik < nFiles; ik++)
924  LineD[ik]->Draw("sam");
925 
926  Posterior->cd();
927  Posterior->Print(canvasname);
928  } //End loop over parameter
929  }
930 }
void RemoveFitter(TH1D *hist, const std::string &name)
KS: Remove fitted TF1 from hist to make comparison easier.
#define MACH3LOG_DEBUG
Definition: MaCh3Logger.h:34
#define MACH3LOG_ERROR
Definition: MaCh3Logger.h:37
#define MACH3LOG_INFO
Definition: MaCh3Logger.h:35
void SetMaCh3LoggerFormat()
Set messaging format of the logger.
Definition: MaCh3Logger.h:60
#define MACH3LOG_WARN
Definition: MaCh3Logger.h:36
Module for processing MCMC chains and producing diagnostic plots.
Type GetFromManager(const YAML::Node &node, const Type defval, const std::string &File, const int Line)
Get content of config file if node is not found take default value specified.
Definition: YamlHelper.h:329
bool CheckNodeExists(const YAML::Node &node, Args... args)
KS: Wrapper function to call the recursive helper.
Definition: YamlHelper.h:60
#define M3OpenConfig(filename)
Macro to simplify calling LoadYaml with file and line info.
Definition: YamlHelper.h:590
Extended ArgumentParser with MaCh3-specific functionality.
Definition: m3argparse.hpp:17
std::unique_ptr< MaCh3ArgumentParser > m_parser
Argument parser for this plugin.
Definition: plugin.hpp:44
virtual ~ProcessMCMCModule()
Destructor.
void CalcBipolarPlot(MCMCProcessor *Processor)
Create bipolar plots for parameter visualization.
std::vector< std::string > FileNames
List of MCMC chain file paths.
std::map< std::string, std::pair< double, double > > GetCustomBinning(const YAML::Node &Settings)
Parse custom binning edges from YAML configuration.
void CalcSavageDickey(MCMCProcessor *Processor)
Calculate Savage-Dickey ratios for Bayes factor estimation.
void CalcParameterEvolution(MCMCProcessor *Processor)
Calculate parameter evolution over MCMC steps.
void CalcBayesFactor(MCMCProcessor *Processor)
Calculate Bayes factors for hypothesis testing.
std::string config
Path to the configuration file.
void GetTrianglePlot(MCMCProcessor *Processor)
Generate triangle plots showing parameter correlations.
void DiagnoseCovarianceMatrix(MCMCProcessor *Processor, const std::string &inputFile)
Diagnose covariance matrix stability across burn-in cuts.
MaCh3ArgumentParser * get_parser() override
Get the argument parser for this module.
std::vector< std::string > TitleNames
List of titles for each chain.
TH2D * TMatrixIntoTH2D(TMatrixDSym *Matrix, const std::string &title)
Convert TMatrixDSym to TH2D histogram for plotting.
void ProcessMCMC(const std::string &inputFile)
Process a single MCMC chain.
void KolmogorovSmirnovTest(const std::vector< std::unique_ptr< MCMCProcessor >> &Processor, const std::unique_ptr< TCanvas > &Posterior, const TString &canvasname)
Perform Kolmogorov-Smirnov test between posterior distributions.
int Run() override
Execute the MCMC processing.
int nFiles
Number of MCMC files being processed.
void MultipleProcessMCMC()
Compare and process multiple MCMC chains.
Class responsible for processing MCMC chains, performing diagnostics, generating plots,...
Definition: MCMCProcessor.h:61
void GetNthParameter(const int param, double &Prior, double &PriorError, TString &Title) const
Get properties of parameter by passing it number.
void Reset2DPosteriors()
Reset 2D posteriors, in case we would like to calculate in again with different BurnInCut.
void GetBayesFactor(const std::vector< std::string > &ParName, const std::vector< std::vector< double >> &Model1Bounds, const std::vector< std::vector< double >> &Model2Bounds, const std::vector< std::vector< std::string >> &ModelNames)
Calculate Bayes factor for vector of params, and model boundaries.
void MakeTrianglePlot(const std::vector< std::string > &ParNames, const std::vector< double > &CredibleIntervals={0.99, 0.90, 0.68 }, const std::vector< Color_t > &CredibleIntervalsColours={kCyan+4, kCyan-2, kCyan-10}, const std::vector< double > &CredibleRegions={0.99, 0.90, 0.68}, const std::vector< Style_t > &CredibleRegionStyle={kDashed, kSolid, kDotted}, const std::vector< Color_t > &CredibleRegionColor={kGreen-3, kGreen-10, kGreen}, const bool CredibleInSigmas=false)
Make fancy triangle plot for selected parameters.
void SetPrintToPDF(const bool PlotOrNot)
Whether to dump all plots into PDF.
Long64_t GetnSteps()
Get Number of Steps that Chain has, for merged chains will not be the same nEntries.
void GetPolarPlot(const std::vector< std::string > &ParNames)
Make funny polar plot.
void ParameterEvolution(const std::vector< std::string > &Names, const std::vector< int > &NIntervals)
Make .gif of parameter evolution.
void GetCovariance(TMatrixDSym *&Cov, TMatrixDSym *&Corr)
Get the post-fit covariances and correlations.
void SetStepCut(const std::string &Cuts)
Set the step cutting by string.
void GetSavageDickey(const std::vector< std::string > &ParName, const std::vector< double > &EvaluationPoint, const std::vector< std::vector< double >> &Bounds)
Calculate Bayes factor for point like hypothesis using SavageDickey.
Custom exception class used throughout MaCh3.
Main namespace for MaCh3 software.
std::unique_ptr< ObjectType > Clone(const ObjectType *obj, const std::string &name="")
KS: Creates a copy of a ROOT-like object and wraps it in a smart pointer.
constexpr static const double _BAD_DOUBLE_
Default value used for double initialisation.
Definition: Core.h:53
constexpr static const int _BAD_INT_
Default value used for int initialisation.
Definition: Core.h:55