MaCh3  2.6.0
Reference Guide
PredictiveThrower.cpp
Go to the documentation of this file.
1 #include "PredictiveThrower.h"
3 #include "TH3.h"
4 
5 //this file is choc full of usage of a root interface that only takes floats, turn this warning off for this CU for now
6 #pragma GCC diagnostic ignored "-Wfloat-conversion"
7 #pragma GCC diagnostic ignored "-Wuseless-cast"
8 
9 // *************************
11 // *************************
12  AlgorithmName = "PredictiveThrower";
13  if(!CheckNodeExists(fitMan->raw(), "Predictive")) {
14  MACH3LOG_ERROR("Predictive is missing in your main yaml config");
15  throw MaCh3Exception(__FILE__ , __LINE__ );
16  }
17 
18  StandardFluctuation = GetFromManager<bool>(fitMan->raw()["Predictive"]["StandardFluctuation"], true, __FILE__, __LINE__ );
19 
20  if(StandardFluctuation) MACH3LOG_INFO("Using standard method of statistical fluctuation");
21  else MACH3LOG_INFO("Using alternative method of statistical fluctuation, which is much slower");
22 
23  ModelSystematic = nullptr;
24  // Use the full likelihood for the Prior/Posterior predictive pvalue
25  FullLLH = GetFromManager<bool>(fitMan->raw()["Predictive"]["FullLLH"], false, __FILE__, __LINE__ );
26  NModelParams = 0;
27 
28  Is_PriorPredictive = Get<bool>(fitMan->raw()["Predictive"]["PriorPredictive"], __FILE__, __LINE__);
29  Ntoys = Get<int>(fitMan->raw()["Predictive"]["Ntoy"], __FILE__, __LINE__);
30 
31  ReweightWeight.resize(Ntoys);
32  PenaltyTerm.resize(Ntoys);
33 }
34 
35 // *************************
36 // Destructor:
38 // *************************
39 }
40 
41 // *************************
42 void PredictiveThrower::SetParamters(std::vector<std::string>& ParameterGroupsNotVaried,
43  std::unordered_set<int>& ParameterOnlyToVary) {
44 // *************************
45  // WARNING This should be removed in the future
46  auto DoNotThrowLegacyCov = GetFromManager<std::vector<std::string>>(fitMan->raw()["Predictive"]["DoNotThrowLegacyCov"], {}, __FILE__, __LINE__);
48  for (size_t i = 0; i < DoNotThrowLegacyCov.size(); ++i) {
49  for (size_t s = 0; s < systematics.size(); ++s) {
50  if (systematics[s]->GetName() == DoNotThrowLegacyCov[i]) {
51  systematics[s]->SetParameters();
52  break;
53  }
54  }
55  }
56 
57  // Set groups to prefit values if they were set to not be varies
58  if(ModelSystematic && ParameterGroupsNotVaried.size() > 0) {
59  ModelSystematic->SetGroupOnlyParameters(ParameterGroupsNotVaried);
60  }
61 
63  if (ModelSystematic && !ParameterOnlyToVary.empty()) {
64  for (int i = 0; i < ModelSystematic->GetNumParams(); ++i) {
65  // KS: If parameter is in map then we are skipping this, otherwise for params that we don't want to vary we simply set it to prior
66  if (ParameterOnlyToVary.find(i) == ParameterOnlyToVary.end()) {
68  }
69  }
70  }
71 }
72 
73 // *************************
75 // *************************
77  for (size_t iPDF = 0; iPDF < samples.size(); iPDF++) {
78  TotalNumberOfSamples += samples[iPDF]->GetNSamples();
79  }
80 
86 
88 
89 
90  for (int sample = 0; sample < TotalNumberOfSamples; ++sample) {
91  MC_Hist_Toy[sample].resize(Ntoys);
92  W2_Hist_Toy[sample].resize(Ntoys);
93  }
94  int counter = 0;
95  for (size_t iPDF = 0; iPDF < samples.size(); iPDF++) {
96  for (int SampleIndex = 0; SampleIndex < samples[iPDF]->GetNSamples(); ++SampleIndex) {
97  SampleInfo[counter].Name = samples[iPDF]->GetSampleTitle(SampleIndex);
98  SampleInfo[counter].LocalId = SampleIndex;
99  SampleInfo[counter].SamHandler = samples[iPDF];
100  SampleInfo[counter].Dimenstion = SampleInfo[counter].SamHandler->GetNDim(SampleIndex);
101  counter++;
102  }
103  }
104  SampleInfo[TotalNumberOfSamples].Name= "Total";
105 }
106 
107 // *************************
108 // Produce MaCh3 toys:
109 void PredictiveThrower::SetupToyGeneration(std::vector<std::string>& ParameterGroupsNotVaried,
110  std::unordered_set<int>& ParameterOnlyToVary,
111  std::vector<const M3::float_t*>& BoundValuePointer,
112  std::vector<std::pair<double, double>>& ParamBounds) {
113 // *************************
114  int counter = 0;
115  for (size_t s = 0; s < systematics.size(); ++s) {
116  auto* MaCh3Params = dynamic_cast<ParameterHandlerGeneric*>(systematics[s]);
117  if(MaCh3Params) {
118  ModelSystematic = MaCh3Params;
119  counter++;
120  }
121  }
122 
124 
125  if(Is_PriorPredictive) {
126  MACH3LOG_INFO("You've chosen to run Prior Predictive Distribution");
127  } else {
128  auto PosteriorFileName = Get<std::string>(fitMan->raw()["Predictive"]["PosteriorFile"], __FILE__, __LINE__);
129  //KS: We use MCMCProcessor to get names of covariances that were actually used to produce given chain
130  MCMCProcessor Processor(PosteriorFileName);
131  Processor.Initialise();
132 
133  // For throwing FD predictions from ND-only chain we have to allow having different yaml configs
134  auto AllowDifferentConfigs = GetFromManager<bool>(fitMan->raw()["Predictive"]["AllowDifferentConfigs"], false, __FILE__, __LINE__);
135 
137  YAML::Node ConfigInChain = Processor.GetCovConfig(kXSecPar);
138  if(ModelSystematic) {
139  YAML::Node ConfigNow = ModelSystematic->GetConfig();
140  if (!compareYAMLNodes(ConfigNow, ConfigInChain))
141  {
142  if(AllowDifferentConfigs){
143  MACH3LOG_WARN("Yaml configs used for your ParameterHandler for chain you want sample from ({}) and one currently initialised are different", PosteriorFileName);
144  } else {
145  MACH3LOG_ERROR("Yaml configs used for your ParameterHandler for chain you want sample from ({}) and one currently initialised are different", PosteriorFileName);
146  throw MaCh3Exception(__FILE__ , __LINE__ );
147  }
148  }
149  }
150  }
151  if(counter > 1) {
152  MACH3LOG_ERROR("Found {} ParmaterHandler inheriting from ParameterHandlerGeneric, I can accept at most 1", counter);
153  throw MaCh3Exception(__FILE__, __LINE__);
154  }
155 
156  for (size_t s = 0; s < systematics.size(); ++s) {
157  NModelParams += systematics[s]->GetNumParams();
158  }
159 
160  if (ModelSystematic) {
161  auto ThrowParamGroupOnly = GetFromManager<std::vector<std::string>>(fitMan->raw()["Predictive"]["ThrowParamGroupOnly"], {}, __FILE__, __LINE__);
162  auto UniqueParamGroup = ModelSystematic->GetUniqueParameterGroups();
163  auto ParameterOnlyToVaryString = GetFromManager<std::vector<std::string>>(fitMan->raw()["Predictive"]["ThrowSinlgeParams"], {}, __FILE__, __LINE__);
164 
165  if (!ThrowParamGroupOnly.empty() && !ParameterOnlyToVaryString.empty()) {
166  MACH3LOG_ERROR("Can't use ThrowParamGroupOnly and ThrowSinlgeParams at the same time");
167  throw MaCh3Exception(__FILE__, __LINE__);
168  }
169 
170  if (!ParameterOnlyToVaryString.empty()) {
171  MACH3LOG_INFO("I will throw only: {}", fmt::join(ParameterOnlyToVaryString, ", "));
172  std::vector<int> ParameterVary(ParameterOnlyToVaryString.size());
173 
174  for (size_t i = 0; i < ParameterOnlyToVaryString.size(); ++i) {
175  ParameterVary[i] = ModelSystematic->GetParIndex(ParameterOnlyToVaryString[i]);
176  if (ParameterVary[i] == M3::_BAD_INT_) {
177  MACH3LOG_ERROR("Can't proceed if param {} is missing", ParameterOnlyToVaryString[i]);
178  throw MaCh3Exception(__FILE__, __LINE__);
179  }
180  }
181  ParameterOnlyToVary = std::unordered_set<int>(ParameterVary.begin(), ParameterVary.end());
182  } else {
183  MACH3LOG_INFO("I have following parameter groups: {}", fmt::join(UniqueParamGroup, ", "));
184  if (ThrowParamGroupOnly.empty()) {
185  MACH3LOG_INFO("I will vary all");
186  } else {
187  std::unordered_set<std::string> throwOnlySet(ThrowParamGroupOnly.begin(), ThrowParamGroupOnly.end());
188  ParameterGroupsNotVaried.clear();
189 
190  for (const auto& group : UniqueParamGroup) {
191  if (throwOnlySet.find(group) == throwOnlySet.end()) {
192  ParameterGroupsNotVaried.push_back(group);
193  }
194  }
195 
196  MACH3LOG_INFO("I will vary: {}", fmt::join(ThrowParamGroupOnly, ", "));
197  MACH3LOG_INFO("Exclude: {}", fmt::join(ParameterGroupsNotVaried, ", "));
198  }
199  }
200  }
201 
202  auto paramNode = fitMan->raw()["Predictive"]["ParameterBounds"];
203  for (const auto& p : paramNode) {
204  // Extract name
205  std::string name = p[0].as<std::string>();
206 
207  // Extract bounds: min and max
208  double minVal = p[1][0].as<double>();
209  double maxVal = p[1][1].as<double>();
210  ParamBounds.emplace_back(minVal, maxVal);
211 
212  for (size_t s = 0; s < systematics.size(); ++s) {
213  for(int iPar = 0; iPar < systematics[s]->GetNParameters(); iPar++){
214  if(systematics[s]->GetParFancyName(iPar) == name){
215  BoundValuePointer.push_back(systematics[s]->RetPointer(iPar));
216  break;
217  }
218  }
219  }
220  if(ParamBounds.size() != BoundValuePointer.size()){
221  MACH3LOG_ERROR("Ddin't find paramter {}", name);
222  throw MaCh3Exception(__FILE__,__LINE__);
223  }
224  MACH3LOG_INFO("Parameter: {} with : [{}, {}]", name, minVal, maxVal);
225  }
226  if(Is_PriorPredictive && ParamBounds.size() > 0) {
227  MACH3LOG_ERROR("Additional bounds not supported by prior predictive right now");
228  throw MaCh3Exception(__FILE__,__LINE__);
229  }
230 }
231 
232 // *************************
233 // Try loading toys
235 // *************************
236  auto PosteriorFileName = Get<std::string>(fitMan->raw()["Predictive"]["PosteriorFile"], __FILE__, __LINE__);
237  // Open the ROOT file
238  int originalErrorWarning = gErrorIgnoreLevel;
239  gErrorIgnoreLevel = kFatal;
240  TFile* file = TFile::Open(PosteriorFileName.c_str(), "READ");
241 
242  gErrorIgnoreLevel = originalErrorWarning;
243  TDirectory* ToyDir = nullptr;
244  if (!file || file->IsZombie()) {
245  return false;
246  } else {
247  // Check for the "toys" directory
248  if ((ToyDir = file->GetDirectory("Toys"))) {
249  MACH3LOG_INFO("Found toys in Posterior file will attempt toy reading");
250  } else {
251  file->Close();
252  delete file;
253  return false;
254  }
255  }
256 
257  // Finally get the TTree branch with the penalty vectors for each of the toy throws
258  TTree* PenaltyTree = static_cast<TTree*>(file->Get("ToySummary"));
259  if (!PenaltyTree) {
260  MACH3LOG_WARN("ToySummary TTree not found in file.");
261  file->Close();
262  delete file;
263  return false;
264  }
265 
266  Ntoys = static_cast<int>(PenaltyTree->GetEntries());
267  int ConfigNtoys = Get<int>(fitMan->raw()["Predictive"]["Ntoy"], __FILE__, __LINE__);;
268  if (Ntoys != ConfigNtoys) {
269  MACH3LOG_WARN("Found different number of toys in saved file than asked to run!");
270  MACH3LOG_INFO("Will read _ALL_ toys in the file");
271  MACH3LOG_INFO("Ntoys in file: {}", Ntoys);
272  MACH3LOG_INFO("Ntoys specified: {}", ConfigNtoys);
273  }
274 
275  PenaltyTerm.resize(Ntoys);
276  ReweightWeight.resize(Ntoys);
277 
278  double Penalty = 0, Weight = 1;
279  PenaltyTree->SetBranchAddress("Penalty", &Penalty);
280  PenaltyTree->SetBranchAddress("Weight", &Weight);
281  PenaltyTree->SetBranchAddress("NModelParams", &NModelParams);
282 
283  for (int i = 0; i < Ntoys; ++i) {
284  PenaltyTree->GetEntry(i);
285  if (FullLLH) {
286  PenaltyTerm[i] = Penalty;
287  } else {
288  PenaltyTerm[i] = 0.0;
289  }
290 
291  ReweightWeight[i] = Weight;
292  }
293  // Resize all vectors and get sample names
295 
296  for (int sample = 0; sample < TotalNumberOfSamples; ++sample) {
297  TH1* DataHist1D = static_cast<TH1*>(ToyDir->Get((SampleInfo[sample].Name + "_data").c_str()));
298  Data_Hist[sample] = M3::Clone(DataHist1D);
299 
300  TH1* MCHist1D = static_cast<TH1*>(ToyDir->Get((SampleInfo[sample].Name + "_mc").c_str()));
301  MC_Nom_Hist[sample] = M3::Clone(MCHist1D);
302 
303  TH1* W2Hist1D = static_cast<TH1*>(ToyDir->Get((SampleInfo[sample].Name + "_w2").c_str()));
304  W2_Nom_Hist[sample] = M3::Clone(W2Hist1D);
305  }
306 
307 
308  for (int iToy = 0; iToy < Ntoys; ++iToy)
309  {
310  if (iToy % 100 == 0) MACH3LOG_INFO(" Loaded toy {}", iToy);
311 
312  for (int sample = 0; sample < TotalNumberOfSamples; ++sample) {
313  TH1* MCHist1D = static_cast<TH1*>(ToyDir->Get((SampleInfo[sample].Name + "_mc_" + std::to_string(iToy)).c_str()));
314  TH1* W2Hist1D = static_cast<TH1*>(ToyDir->Get((SampleInfo[sample].Name + "_w2_" + std::to_string(iToy)).c_str()));
315 
316  MC_Hist_Toy[sample][iToy] = M3::Clone(MCHist1D);
317  W2_Hist_Toy[sample][iToy] = M3::Clone(W2Hist1D);
318  }
319  }
320 
321  file->Close();
322  delete file;
323  return true;
324 }
325 
326 // *************************
327 std::vector<std::string> PredictiveThrower::GetStoredFancyName(ParameterHandlerBase* Systematics) const {
328 // *************************
329  TDirectory * ogdir = gDirectory;
330 
331  std::vector<std::string> FancyNames;
332  std::string Name = std::string("Config_") + Systematics->GetName();
333  auto PosteriorFileName = Get<std::string>(fitMan->raw()["Predictive"]["PosteriorFile"], __FILE__, __LINE__);
334 
335  TFile* file = TFile::Open(PosteriorFileName.c_str(), "READ");
336  TDirectory* CovarianceFolder = file->GetDirectory("CovarianceFolder");
337 
338  TMacro* FoundMacro = static_cast<TMacro*>(CovarianceFolder->Get(Name.c_str()));
339  if(FoundMacro == nullptr) {
340  file->Close();
341  delete file;
342  if(ogdir){ ogdir->cd(); }
343 
344  return FancyNames;
345  }
346  MACH3LOG_DEBUG("Found config for {}", Name);
347  YAML::Node Settings = TMacroToYAML(*FoundMacro);
348 
349  int params = int(Settings["Systematics"].size());
350  FancyNames.resize(params);
351  int iPar = 0;
352  for (auto const &param : Settings["Systematics"]) {
353  FancyNames[iPar] = Get<std::string>(param["Systematic"]["Names"]["FancyName"], __FILE__ , __LINE__);
354  iPar++;
355  }
356  file->Close();
357  delete file;
358  if(ogdir){ ogdir->cd(); }
359  return FancyNames;
360 }
361 
362 
363 // *************************
364 void PredictiveThrower::WriteToy(TDirectory* ToyDirectory,
365  TDirectory* Toy_1DDirectory,
366  TDirectory* Toy_2DDirectory,
367  const int iToy) {
368 // *************************
369  int SampleCounter = 0;
370  for (size_t iPDF = 0; iPDF < samples.size(); iPDF++)
371  {
372  auto* SampleHandler = samples[iPDF];
373  for (int iSample = 0; iSample < SampleHandler->GetNSamples(); ++iSample)
374  {
375  ToyDirectory->cd();
376 
377  auto SampleName = SampleHandler->GetSampleTitle(iSample);
378  const TH1* MCHist = SampleHandler->GetMCHist(iSample);
379  MC_Hist_Toy[SampleCounter][iToy] = M3::Clone(MCHist, SampleName + "_mc_" + std::to_string(iToy));
380  MC_Hist_Toy[SampleCounter][iToy]->Write();
381 
382  const TH1* W2Hist = SampleHandler->GetW2Hist(iSample);
383  W2_Hist_Toy[SampleCounter][iToy] = M3::Clone(W2Hist, SampleName + "_w2_" + std::to_string(iToy));
384  W2_Hist_Toy[SampleCounter][iToy]->Write();
385 
386  // now get 1D projection for every dimension
387  Toy_1DDirectory->cd();
388  for(int iDim = 0; iDim < SampleHandler->GetNDim(iSample); iDim++) {
389  std::string ProjectionName = SampleHandler->GetKinVarName(iSample, iDim);
390  std::string ProjectionSuffix = "_1DProj" + std::to_string(iDim) + "_" + std::to_string(iToy);
391 
392  auto hist = SampleHandler->Get1DVarHist(iSample, ProjectionName);
393  hist->SetTitle((SampleName + ProjectionSuffix).c_str());
394  hist->SetName((SampleName + ProjectionSuffix).c_str());
395  hist->Write();
396  }
397 
398  Toy_2DDirectory->cd();
399  // now get 2D projection for every combination
400  for(int iDim1 = 0; iDim1 < SampleHandler->GetNDim(iSample); iDim1++) {
401  for (int iDim2 = iDim1 + 1; iDim2 < SampleHandler->GetNDim(iSample); ++iDim2) {
402  // Get the names for the two dimensions
403  std::string XVarName = SampleHandler->GetKinVarName(iSample, iDim1);
404  std::string YVarName = SampleHandler->GetKinVarName(iSample, iDim2);
405 
406  // Get the 2D histogram for this pair
407  auto hist2D = SampleHandler->Get2DVarHist(iSample, XVarName, YVarName);
408 
409  // Write the histogram
410  std::string suffix2D = "_2DProj_" + std::to_string(iDim1) + "_vs_" + std::to_string(iDim2) + "_" + std::to_string(iToy);
411  hist2D->SetTitle((SampleName + suffix2D).c_str());
412  hist2D->SetName((SampleName + suffix2D).c_str());
413  hist2D->Write();
414  }
415  }
416  SampleCounter++;
417  }
418  }
419 }
420 
421 // *************************
422 void PredictiveThrower::WriteByModeToys(TDirectory* ByModeDirectory,
423  const int iToy) {
424 // *************************
425  for (size_t iPDF = 0; iPDF < samples.size(); iPDF++)
426  {
427  auto* SampleHandler = samples[iPDF];
428  auto* modes = SampleHandler->GetMaCh3Modes();
429  for (int iSample = 0; iSample < SampleHandler->GetNSamples(); ++iSample)
430  {
431  ByModeDirectory->cd();
432 
433  auto SampleName = SampleHandler->GetSampleTitle(iSample);
434  for (int iMode = 0; iMode < modes->GetNModes()+1; ++iMode) {
435  auto ModeName = modes->GetMaCh3ModeName(iMode);
436  for(int iDim = 0; iDim < SampleHandler->GetNDim(iSample); iDim++) {
437  std::string ProjectionName = SampleHandler->GetKinVarName(iSample, iDim);
438  std::string PlotSuffix = "_1DProj" + std::to_string(iDim) + "_" + ModeName + "_" + std::to_string(iToy);
439 
440  auto hist = SampleHandler->Get1DVarHistByModeAndChannel(iSample, ProjectionName, iMode);
441  hist->SetTitle((SampleName + PlotSuffix).c_str());
442  hist->SetName((SampleName + PlotSuffix).c_str());
443  hist->Write();
444  } // end loop over dimension
445  } // end loop over mode
446  } // end loop over sample
447  } // end loop over sample handler objects
448 }
449 
450 // *************************
451 bool CheckBounds(const std::vector<const M3::float_t*>& BoundValuePointer,
452  const std::vector<std::pair<double,double>>& ParamBounds) {
453 // *************************
454  for (size_t i = 0; i < BoundValuePointer.size(); ++i) {
455  const double val = *(BoundValuePointer[i]);
456  const double minVal = ParamBounds[i].first;
457  const double maxVal = ParamBounds[i].second;
458 
459  if (val < minVal || val > maxVal)
460  return false; // out of bounds
461  }
462  return true; // all values are within bounds
463 }
464 
465 // *************************
466 // Produce MaCh3 toys:
468 // *************************
469  // If we found toys then skip process of making new toys
470  if(LoadToys()) return;
471 
473  std::vector<std::string> ParameterGroupsNotVaried;
475  std::unordered_set<int> ParameterOnlyToVary;
476  // For study where one would like to apply bounds
477  std::vector<const M3::float_t*> BoundValuePointer;
478  std::vector<std::pair<double, double>> ParamBounds;
479 
480  // Setup useful information for toy generation
481  SetupToyGeneration(ParameterGroupsNotVaried, ParameterOnlyToVary,
482  BoundValuePointer, ParamBounds);
483 
484  auto PosteriorFileName = Get<std::string>(fitMan->raw()["Predictive"]["PosteriorFile"], __FILE__, __LINE__);
485 
486  MACH3LOG_INFO("Starting {}", __func__);
487 
488  outputFile->cd();
489  double Penalty = 0, Weight = 1.;
490  int Draw = 0;
491 
492  TTree *ToyTree = new TTree("ToySummary", "ToySummary");
493  ToyTree->Branch("Penalty", &Penalty, "Penalty/D");
494  ToyTree->Branch("Weight", &Weight, "Weight/D");
495  ToyTree->Branch("Draw", &Draw, "Draw/I");
496  ToyTree->Branch("NModelParams", &NModelParams, "NModelParams/I");
497 
498  // KS: define branches so we can keep track of what params we are throwing
499  std::vector<double> ParamValues(NModelParams);
500  std::vector<const M3::float_t*> ParampPointers(NModelParams);
501  int ParamCounter = 0;
502  for (size_t iSys = 0; iSys < systematics.size(); iSys++)
503  {
504  for (int iPar = 0; iPar < systematics[iSys]->GetNumParams(); iPar++)
505  {
506  ParampPointers[ParamCounter] = systematics[iSys]->RetPointer(iPar);
507  std::string Name = systematics[iSys]->GetParFancyName(iPar);
508  //CW: Also strip out - signs because it messes up TBranches
509  while (Name.find("-") != std::string::npos) {
510  Name.replace(Name.find("-"), 1, std::string("_"));
511  }
512  ToyTree->Branch(Name.c_str(), &ParamValues[ParamCounter], (Name + "/D").c_str());
513  ParamCounter++;
514  }
515  }
516  TDirectory* ToyDirectory = outputFile->mkdir("Toys");
517  ToyDirectory->cd();
518  int SampleCounter = 0;
519  for (size_t iPDF = 0; iPDF < samples.size(); iPDF++)
520  {
521  auto* MaCh3Sample = samples[iPDF];
522  for (int SampleIndex = 0; SampleIndex < MaCh3Sample->GetNSamples(); ++SampleIndex)
523  {
524  // Get nominal spectra and event rates
525  const TH1* DataHist = MaCh3Sample->GetDataHist(SampleIndex);
526  Data_Hist[SampleCounter] = M3::Clone(DataHist, MaCh3Sample->GetSampleTitle(SampleIndex) + "_data");
527  Data_Hist[SampleCounter]->Write((MaCh3Sample->GetSampleTitle(SampleIndex) + "_data").c_str());
528 
529  const TH1* MCHist = MaCh3Sample->GetMCHist(SampleIndex);
530  MC_Nom_Hist[SampleCounter] = M3::Clone(MCHist, MaCh3Sample->GetSampleTitle(SampleIndex) + "_mc");
531  MC_Nom_Hist[SampleCounter]->Write((MaCh3Sample->GetSampleTitle(SampleIndex) + "_mc").c_str());
532 
533  const TH1* W2Hist = MaCh3Sample->GetW2Hist(SampleIndex);
534  W2_Nom_Hist[SampleCounter] = M3::Clone(W2Hist, MaCh3Sample->GetSampleTitle(SampleIndex) + "_w2");
535  W2_Nom_Hist[SampleCounter]->Write((MaCh3Sample->GetSampleTitle(SampleIndex) + "_w2").c_str());
536  SampleCounter++;
537  }
538  }
539 
540  TDirectory* Toy_1DDirectory = outputFile->mkdir("Toys_1DHistVar");
541  TDirectory* Toy_2DDirectory = outputFile->mkdir("Toys_2DHistVar");
542  auto doByMode = GetFromManager<bool>(fitMan->raw()["Predictive"]["ByMode"], false, __FILE__, __LINE__);
543  TDirectory* ByModeDirectory = nullptr;
544  if(doByMode) ByModeDirectory = outputFile->mkdir("Toys_ByMode");
545  auto ReweightNames = GetFromManager<std::vector<std::string>>(fitMan->raw()["Predictive"]["ReweightNames"],
546  {"Weight"}, __FILE__, __LINE__);
547  bool doReweight = false;
548  std::vector<double> reweight_weight(ReweightNames.size(), 1.0);
549 
551  std::vector<std::vector<double>> branch_vals(systematics.size());
552  std::vector<std::vector<std::string>> branch_name(systematics.size());
553 
554  TChain* PosteriorFile = nullptr;
555  unsigned int burn_in = 0;
556  unsigned int maxNsteps = 0;
557  unsigned int Step = 0;
558  if(!Is_PriorPredictive)
559  {
560  PosteriorFile = new TChain("posteriors");
561  PosteriorFile->Add(PosteriorFileName.c_str());
562 
563  PosteriorFile->SetBranchAddress("step", &Step);
564  doReweight = true;
565  for (size_t i = 0; i < ReweightNames.size(); ++i) {
566  const auto& name = ReweightNames[i];
567  if (PosteriorFile->GetBranch(name.c_str())) {
568  PosteriorFile->SetBranchStatus(name.c_str(), true);
569  PosteriorFile->SetBranchAddress(name.c_str(), &reweight_weight[i]);
570  } else {
571  MACH3LOG_WARN("Missing reweight branch '{}' -> disabling ALL reweighting", name);
572  doReweight = false;
573  }
574  }
575 
576  for (size_t s = 0; s < systematics.size(); ++s) {
577  auto fancy_names = GetStoredFancyName(systematics[s]);
578  systematics[s]->MatchMaCh3OutputBranches(PosteriorFile, branch_vals[s], branch_name[s], fancy_names);
579  }
580 
581  //Get the burn-in from the config
582  burn_in = Get<unsigned int>(fitMan->raw()["Predictive"]["BurnInSteps"], __FILE__, __LINE__);
583 
584  //DL: Adding sanity check for chains shorter than burn in
585  maxNsteps = static_cast<unsigned int>(PosteriorFile->GetMaximum("step"));
586  if(burn_in >= maxNsteps)
587  {
588  MACH3LOG_ERROR("You are running on a chain shorter than burn in cut");
589  MACH3LOG_ERROR("Maximal value of nSteps: {}, burn in cut {}", maxNsteps, burn_in);
590  MACH3LOG_ERROR("You will run into infinite loop");
591  MACH3LOG_ERROR("You can make new chain or modify burn in cut");
592  throw MaCh3Exception(__FILE__,__LINE__);
593  }
594  }
595 
596  TStopwatch TempClock;
597  TempClock.Start();
598  for(int i = 0; i < Ntoys; i++)
599  {
600  if(Ntoys >= 10 && i % (Ntoys/10) == 0) {
602  }
603  if(!Is_PriorPredictive){
604  int entry = 0;
605  Step = 0;
606  // KS This allow to set additional bounds like mass ordering
607  bool WithinBounds = false;
608  //YSP: Ensures you get an entry from the mcmc even when burn_in is set to zero (Although not advised :p ).
609  //Take 200k burn in steps, WP: Eb C in 1st peaky
610  // If we have combined chains by hadd need to check the step in the chain
611  // Note, entry is not necessarily same as step due to merged ROOT files, so can't choose entry in the range BurnIn - nEntries :(
612  while(Step < burn_in || !WithinBounds) {
613  entry = random->Integer(static_cast<unsigned int>(PosteriorFile->GetEntries()));
614  PosteriorFile->GetEntry(entry);
615  // KS: This might be bit hacky... but BoundValuePointer refer to values in ParameterHandler
616  // so we need to update them
617  if(BoundValuePointer.size() > 0) {
618  for (size_t s = 0; s < systematics.size(); ++s) {
619  systematics[s]->SetParameters(branch_vals[s]);
620  }
621  }
622  WithinBounds = CheckBounds(BoundValuePointer, ParamBounds);
623  }
624  Draw = entry;
625  }
626  for (size_t s = 0; s < systematics.size(); ++s)
627  {
628  //KS: Below line can help you get prior predictive distributions which are helpful for getting pre and post ND fit spectra
629  //YSP: If not set in the config, the code runs SK Posterior Predictive distributions by default. If true, then the code runs SK prior predictive.
630  if(Is_PriorPredictive) {
631  systematics[s]->ThrowParameters();
632  } else {
633  systematics[s]->SetParameters(branch_vals[s]);
634  }
635  }
636 
637  // This set some params to prior value this way you can evaluate errors from subset of errors
638  SetParamters(ParameterGroupsNotVaried, ParameterOnlyToVary);
639 
640  Penalty = 0;
641  if(FullLLH) {
642  for (size_t s = 0; s < systematics.size(); ++s) {
643  //KS: do times 2 because banff reports chi2
644  Penalty = 2.0 * systematics[s]->GetLikelihood();
645  }
646  }
647 
648  PenaltyTerm[i] = Penalty;
649  Weight = 1.;
650  if(doReweight) {
651  for (size_t iWeight = 0; iWeight < reweight_weight.size(); ++iWeight) {
652  Weight *= reweight_weight[iWeight];
653  }
654  }
655  ReweightWeight[i] = Weight;
656 
657  for (size_t iPDF = 0; iPDF < samples.size(); iPDF++) {
658  samples[iPDF]->Reweight();
659  }
660  // Save histograms to file
661  WriteToy(ToyDirectory, Toy_1DDirectory, Toy_2DDirectory, i);
662  if(doByMode) WriteByModeToys(ByModeDirectory, i);
663 
664  // Fill parameter value so we know throw values
665  for (size_t iPar = 0; iPar < ParamValues.size(); iPar++) {
666  ParamValues[iPar] = *ParampPointers[iPar];
667  }
668 
669  ToyTree->Fill();
670  }//end of toys loop
671  TempClock.Stop();
672 
673  if(PosteriorFile) delete PosteriorFile;
674  ToyDirectory->Close(); delete ToyDirectory;
675  Toy_1DDirectory->Close(); delete Toy_1DDirectory;
676  Toy_2DDirectory->Close(); delete Toy_2DDirectory;
677  if(doByMode){
678  ByModeDirectory->Close();
679  delete ByModeDirectory;
680  }
681 
682  outputFile->cd();
683  ToyTree->Write(); delete ToyTree;
684 
685  MACH3LOG_INFO("{} took {:.2f}s to finish for {} toys", __func__, TempClock.RealTime(), Ntoys);
686 }
687 
688 // *************************
689 void PredictiveThrower::Study1DProjections(const std::vector<TDirectory*>& SampleDirectories) const {
690 // *************************
691  MACH3LOG_INFO("Starting {}", __func__);
692 
693  TDirectory * ogdir = gDirectory;
694  auto PosteriorFileName = Get<std::string>(fitMan->raw()["Predictive"]["PosteriorFile"], __FILE__, __LINE__);
695  // Open the ROOT file
696  int originalErrorWarning = gErrorIgnoreLevel;
697  gErrorIgnoreLevel = kFatal;
698 
699  TFile* file = TFile::Open(PosteriorFileName.c_str(), "READ");
700 
701  gErrorIgnoreLevel = originalErrorWarning;
702  TDirectory* ToyDir = file->GetDirectory("Toys_1DHistVar");
703  // If toys not amiable in posterior file this means they must be in output file
704  if(ToyDir == nullptr) {
705  ToyDir = outputFile->GetDirectory("Toys_1DHistVar");
706  }
707  // [sample], [toy], [dim]
708  std::vector<std::vector<std::vector<std::unique_ptr<TH1D>>>> ProjectionToys(TotalNumberOfSamples);
709  for (int sample = 0; sample < TotalNumberOfSamples; ++sample) {
710  ProjectionToys[sample].resize(Ntoys);
711  const int nDims = SampleInfo[sample].Dimenstion;
712  for (int iToy = 0; iToy < Ntoys; ++iToy) {
713  ProjectionToys[sample][iToy].resize(nDims);
714  }
715  }
716 
717  for (int iToy = 0; iToy < Ntoys; ++iToy) {
718  if (iToy % 100 == 0) MACH3LOG_INFO(" Loaded Projection toys {}", iToy);
719  for (int sample = 0; sample < TotalNumberOfSamples; ++sample) {
720  const int nDims = SampleInfo[sample].Dimenstion;
721  for(int iDim = 0; iDim < nDims; iDim ++){
722  std::string ProjectionSuffix = "_1DProj" + std::to_string(iDim) + "_" + std::to_string(iToy);
723  TH1D* MCHist1D = static_cast<TH1D*>(ToyDir->Get((SampleInfo[sample].Name + ProjectionSuffix).c_str()));
724  ProjectionToys[sample][iToy][iDim] = M3::Clone(MCHist1D);
725  }
726  } // end loop over samples
727  } // end loop over toys
728  file->Close(); delete file;
729  if(ogdir){ ogdir->cd(); }
730 
731  ProduceSpectra(ProjectionToys, SampleDirectories, "mc");
732  for (int sample = 0; sample < TotalNumberOfSamples; ++sample) {
733  const int nDims = SampleInfo[sample].Dimenstion;
734  // KS: We only care about doing projections for 2D, for 1D we have well 1D, for beyond 2D we have flattened TH1D
735  if(nDims == 2){
736  auto hist = Data_Hist[sample].get();
737  SampleDirectories[sample]->cd();
738 
739  std::string nameX = "Data_" + SampleInfo[sample].Name + "_Dim0";
740  std::string nameY = "Data_" + SampleInfo[sample].Name + "_Dim1";
741 
742  if(std::string(hist->ClassName()) == "TH2Poly") {
743  TAxis* xax = ProjectionToys[sample][0][0]->GetXaxis();
744  TAxis* yax = ProjectionToys[sample][0][1]->GetXaxis();
745 
746  std::vector<double> XBinning(xax->GetNbins()+1);
747  std::vector<double> YBinning(yax->GetNbins()+1);
748 
749  for(int i=0;i<=xax->GetNbins();++i)
750  XBinning[i] = xax->GetBinLowEdge(i+1);
751 
752  for(int i=0;i<=yax->GetNbins();++i)
753  YBinning[i] = yax->GetBinLowEdge(i+1);
754 
755  TH1D* ProjectionX = PolyProjectionX(static_cast<TH2Poly*>(hist), nameX.c_str(), XBinning, false);
756  TH1D* ProjectionY = PolyProjectionY(static_cast<TH2Poly*>(hist), nameY.c_str(), YBinning, false);
757 
758  ProjectionX->SetDirectory(nullptr);
759  ProjectionY->SetDirectory(nullptr);
760 
761  ProjectionX->Write(nameX.c_str());
762  ProjectionY->Write(nameY.c_str());
763 
764  delete ProjectionX;
765  delete ProjectionY;
766  } else { //TH2D
767  TH1D* ProjectionX = static_cast<TH2D*>(hist)->ProjectionX(nameX.c_str());
768  TH1D* ProjectionY = static_cast<TH2D*>(hist)->ProjectionY(nameY.c_str());
769 
770  ProjectionX->SetDirectory(nullptr);
771  ProjectionY->SetDirectory(nullptr);
772 
773  ProjectionX->Write(nameX.c_str());
774  ProjectionY->Write(nameY.c_str());
775  delete ProjectionX;
776  delete ProjectionY;
777  }
778  }
779  }
780 }
781 
782 // *************************
783 void PredictiveThrower::StudyByMode1DProjections(const std::vector<TDirectory*>& SampleDirectories) const {
784 // *************************
785  MACH3LOG_INFO("Starting {}", __func__);
786 
787  TDirectory * ogdir = gDirectory;
788  auto PosteriorFileName = Get<std::string>(fitMan->raw()["Predictive"]["PosteriorFile"], __FILE__, __LINE__);
789  // Open the ROOT file
790  int originalErrorWarning = gErrorIgnoreLevel;
791  gErrorIgnoreLevel = kFatal;
792 
793  TFile* file = TFile::Open(PosteriorFileName.c_str(), "READ");
794 
795  gErrorIgnoreLevel = originalErrorWarning;
796  TDirectory* ToyDir = file->GetDirectory("Toys_ByMode");
797  // If toys not amiable in posterior file this means they must be in output file
798  if(ToyDir == nullptr) {
799  ToyDir = outputFile->GetDirectory("Toys_ByMode");
800  }
804  auto* mode = SampleInfo[0].SamHandler->GetMaCh3Modes();
805  auto NModes = mode->GetNModes()+1;
806  // [mode], [sample], [toy], [dim]
807  std::vector<std::vector<std::vector<std::vector<std::unique_ptr<TH1D>>>>> ProjectionToys(NModes);
808  for(int iMode = 0; iMode < NModes; iMode++) {
809  ProjectionToys[iMode].resize(TotalNumberOfSamples);
810  for (int sample = 0; sample < TotalNumberOfSamples; ++sample) {
811  ProjectionToys[iMode][sample].resize(Ntoys);
812  const int nDims = SampleInfo[sample].Dimenstion;
813  for (int iToy = 0; iToy < Ntoys; ++iToy) {
814  ProjectionToys[iMode][sample][iToy].resize(nDims);
815  }
816  }
817  }
818 
819  for (int iToy = 0; iToy < Ntoys; ++iToy) {
820  if (iToy % 100 == 0) MACH3LOG_INFO(" Loaded Projection toys {}", iToy);
821  for(int iMode = 0; iMode < NModes; iMode++) {
822  auto ModeName = mode->GetMaCh3ModeName(iMode);
823  for (int sample = 0; sample < TotalNumberOfSamples; ++sample) {
824  const int nDims = SampleInfo[sample].Dimenstion;
825  for(int iDim = 0; iDim < nDims; iDim ++) {
826  std::string ProjectionSuffix = "_1DProj" + std::to_string(iDim) + "_" + ModeName + "_" + std::to_string(iToy);
827  TH1D* MCHist1D = static_cast<TH1D*>(ToyDir->Get((SampleInfo[sample].Name + ProjectionSuffix).c_str()));
828  ProjectionToys[iMode][sample][iToy][iDim] = M3::Clone(MCHist1D);
829  }
830  }
831  } // end loop over samples
832  } // end loop over toys
833 
834  // ByMode directory
835  std::vector<TDirectory*> ModeDirectory(TotalNumberOfSamples);
836  for(int iSample = 0; iSample < TotalNumberOfSamples; iSample++) {
837  ModeDirectory[iSample] = SampleDirectories[iSample]->mkdir("ByMode");
838  }
839  // Produce By Mode Spectra
840  for(int iMode = 0; iMode < NModes; iMode++) {
841  auto ModeName = mode->GetMaCh3ModeName(iMode);
842  ProduceSpectra(ProjectionToys[iMode], ModeDirectory, ModeName, false);
843  }
844  for(int iSample = 0; iSample < TotalNumberOfSamples; iSample++) {
845  ModeDirectory[iSample]->Close();
846  delete ModeDirectory[iSample];
847  }
848  file->Close(); delete file;
849  if(ogdir){ ogdir->cd(); }
850 }
851 
852 // *************************
853 void PredictiveThrower::ProduceSpectra(const std::vector<std::vector<std::vector<std::unique_ptr<TH1D>>>>& Toys,
854  const std::vector<TDirectory*>& SampleDirectories,
855  const std::string suffix,
856  const bool DoSummary) const {
857 // *************************
858  std::vector<std::vector<double>> MaxValue(TotalNumberOfSamples);
859 
860  // 1. Create Max value
861  for (int sample = 0; sample < TotalNumberOfSamples; ++sample) {
862  const int nDims = SampleInfo[sample].Dimenstion;
863  MaxValue[sample].assign(nDims, 0);
864  }
865 
866  // 2. Find maximum entries over all toys
867  #ifdef MULTITHREAD
868  #pragma omp parallel for
869  #endif
870  for (int sample = 0; sample < TotalNumberOfSamples; ++sample) {
871  for (int toy = 0; toy < Ntoys; ++toy) {
872  const int nDims = SampleInfo[sample].Dimenstion;
873  for (int dim = 0; dim < nDims; dim++) {
874  double max_val = Toys[sample][toy][dim]->GetMaximum();
875  MaxValue[sample][dim] = std::max(MaxValue[sample][dim], max_val);
876  }
877  }
878  }
879 
880  // 3. Make actual spectra histogram (this is because making ROOT histograms is not save)
881  // And we now have actual max values
882  std::vector<std::vector<std::unique_ptr<TH2D>>> Spectra(TotalNumberOfSamples);
883  for (int sample = 0; sample < TotalNumberOfSamples; ++sample) {
884  const int nDims = SampleInfo[sample].Dimenstion;
885  Spectra[sample].resize(nDims);
886  for (int dim = 0; dim < nDims; dim++) {
887  // Get MC histogram x-axis binning
888  TH1D* refHist = Toys[sample][0][dim].get();
889 
890  const int n_bins_x = refHist->GetNbinsX();
891  std::vector<double> x_bin_edges(n_bins_x + 1);
892  for (int b = 0; b < n_bins_x; ++b) {
893  x_bin_edges[b] = refHist->GetXaxis()->GetBinLowEdge(b + 1);
894  }
895  x_bin_edges[n_bins_x] = refHist->GetXaxis()->GetBinUpEdge(n_bins_x);
896 
897  constexpr int n_bins_y = 400;
898  constexpr double y_min = 0.0;
899  const double y_max = MaxValue[sample][dim] * 1.05;
900 
901  // Create TH2D with variable binning on x axis
902  Spectra[sample][dim] = std::make_unique<TH2D>(
903  (SampleInfo[sample].Name + "_" + suffix + "_dim" + std::to_string(dim)).c_str(), // name
904  (SampleInfo[sample].Name + "_" + suffix + "_dim" + std::to_string(dim)).c_str(), // title
905  n_bins_x, x_bin_edges.data(), // x axis bins
906  n_bins_y, y_min, y_max // y axis bins
907  );
908 
909  Spectra[sample][dim]->GetXaxis()->SetTitle(refHist->GetXaxis()->GetTitle());
910  Spectra[sample][dim]->GetYaxis()->SetTitle("Events");
911 
912  Spectra[sample][dim]->SetDirectory(nullptr);
913  Spectra[sample][dim]->Sumw2(true);
914  }
915  }
916 
917  // 4. now we can actually fill our projections
918  #ifdef MULTITHREAD
919  #pragma omp parallel for collapse(2)
920  #endif
921  for (int sample = 0; sample < TotalNumberOfSamples; ++sample) {
922  for (int toy = 0; toy < Ntoys; ++toy) {
923  const int nDims = SampleInfo[sample].Dimenstion;
924  for (int dim = 0; dim < nDims; dim++) {
925  FastViolinFill(Spectra[sample][dim].get(), Toys[sample][toy][dim].get());
926  }
927  }
928  }
929 
930  // 5. Save histograms which is not thread save
931  for (int sample = 0; sample < TotalNumberOfSamples; ++sample) {
932  SampleDirectories[sample]->cd();
933  const int nDims = SampleInfo[sample].Dimenstion;
934  for (long unsigned int dim = 0; dim < Spectra[sample].size(); dim++) {
935  Spectra[sample][dim]->Write();
936  // For case of 2D make additional histograms
937  if(nDims == 2 && DoSummary) {
938  const std::string name = SampleInfo[sample].Name + "_" + suffix+ "_PostPred_dim" + std::to_string(dim);
939  auto Summary = MakeSummaryFromSpectra(Spectra[sample][dim].get(), name);
940  Summary->Write();
941  }
942  }
943  }
944 }
945 
946 // *************************
947 std::string PredictiveThrower::GetBinName(TH1* hist,
948  const bool uniform,
949  const int Dim,
950  const std::vector<int>& bins) const {
951 // *************************
952  std::string BinName = "";
953  if(Dim == 1) { // True 1D distribution using TH1D
954  const int b = bins[0];
955  const TAxis* ax = hist->GetXaxis();
956  const double low = ax->GetBinLowEdge(b);
957  const double up = ax->GetBinUpEdge(b);
958 
959  BinName = fmt::format("Dim0 ({:g}, {:g})", low, up);
960  } else if (Dim == 2) { // True 2D dsitrubitons
961  if(uniform == true) { //using TH2D
962  const int bx = bins[0];
963  const int by = bins[1];
964  const TAxis* ax = hist->GetXaxis();
965  const TAxis* ay = hist->GetYaxis();
966  BinName = fmt::format("Dim0 ({:g}, {:g}), ", ax->GetBinLowEdge(bx), ax->GetBinUpEdge(bx));
967  BinName += fmt::format("Dim1 ({:g}, {:g})", ay->GetBinLowEdge(by), ay->GetBinUpEdge(by));
968  } else { // using TH2Poly
969  TH2PolyBin* bin = static_cast<TH2PolyBin*>(static_cast<TH2Poly*>(hist)->GetBins()->At(bins[0]-1));
970  // Just make a little fancy name
971  BinName += fmt::format("Dim{} ({:g}, {:g})", 0, bin->GetXMin(), bin->GetXMax());
972  BinName += fmt::format("Dim{} ({:g}, {:g})", 1, bin->GetYMin(), bin->GetYMax());
973  }
974  } else { // N-dimensional distribution using flatten TH1D
975  BinName = hist->GetXaxis()->GetBinLabel(bins[0]);
976  }
977  return BinName;
978 }
979 
980 // *************************
981 std::vector<std::unique_ptr<TH1D>> PredictiveThrower::PerBinHistogram(TH1* hist,
982  const int SampleId,
983  const int Dim,
984  const std::string& suffix) const {
985 // *************************
986  std::vector<std::unique_ptr<TH1D>> PosteriorHistVec;
987  constexpr int nBins = 100;
988  const std::string Sample_Name = SampleInfo[SampleId].Name;
989  if (Dim == 2) {
990  if(std::string(hist->ClassName()) == "TH2Poly") {
991  for (int i = 1; i <= static_cast<TH2Poly*>(hist)->GetNumberOfBins(); ++i) {
992  std::string ProjName = fmt::format("{} {} Bin: {}",
993  Sample_Name, suffix,
994  GetBinName(hist, false, Dim, {i}));
995  // KS: When a histogram is created with an axis lower limit greater or equal to its upper limit ROOT will automatically adjust histogram range
996  // https://root.cern.ch/doc/master/classTH1.html#auto-bin
997  auto PosteriorHist = std::make_unique<TH1D>(ProjName.c_str(), ProjName.c_str(), nBins, 1, -1);
998  PosteriorHist->SetDirectory(nullptr);
999  PosteriorHist->GetXaxis()->SetTitle("Events");
1000  PosteriorHistVec.push_back(std::move(PosteriorHist));
1001  } //end loop over bin
1002  } else {
1003  int nbinsx = hist->GetNbinsX();
1004  int nbinsy = hist->GetNbinsY();
1005  for (int iy = 1; iy <= nbinsy; ++iy) {
1006  for (int ix = 1; ix <= nbinsx; ++ix) {
1007  std::string ProjName = fmt::format("{} {} Bin: {}",
1008  Sample_Name, suffix,
1009  GetBinName(hist, true, Dim, {ix,iy}));
1010  //KS: When a histogram is created with an axis lower limit greater or equal to its upper limit ROOT will automatically adjust histogram range
1011  // https://root.cern.ch/doc/master/classTH1.html#auto-bin
1012  auto PosteriorHist = std::make_unique<TH1D>(ProjName.c_str(), ProjName.c_str(), nBins, 1, -1);
1013  PosteriorHist->SetDirectory(nullptr);
1014  PosteriorHist->GetXaxis()->SetTitle("Events");
1015  PosteriorHistVec.push_back(std::move(PosteriorHist));
1016  }
1017  }
1018  }
1019  } else {
1020  int nbinsx = hist->GetNbinsX();
1021  PosteriorHistVec.reserve(nbinsx);
1022  for (int i = 1; i <= nbinsx; ++i) {
1023  std::string ProjName = fmt::format("{} {} Bin: {}",
1024  Sample_Name, suffix,
1025  GetBinName(hist, true, Dim, {i}));
1026  //KS: When a histogram is created with an axis lower limit greater or equal to its upper limit ROOT will automatically adjust histogram range
1027  // https://root.cern.ch/doc/master/classTH1.html#auto-bin
1028  auto PosteriorHist = std::make_unique<TH1D>(ProjName.c_str(), ProjName.c_str(), nBins, 1, -1);
1029  PosteriorHist->SetDirectory(nullptr);
1030  PosteriorHist->GetXaxis()->SetTitle("Events");
1031  PosteriorHistVec.push_back(std::move(PosteriorHist));
1032  }
1033  }
1034  return PosteriorHistVec;
1035 }
1036 
1037 // *************************
1038 std::vector<std::unique_ptr<TH1>> PredictiveThrower::MakePredictive(const std::vector<std::vector<std::unique_ptr<TH1>>>& Toys,
1039  const std::vector<TDirectory*>& Directory,
1040  const std::string& suffix,
1041  const bool DebugHistograms,
1042  const bool WriteHist) {
1043 // *************************
1044  std::vector<std::unique_ptr<TH1>> PostPred(TotalNumberOfSamples);
1045  std::vector<std::vector<std::unique_ptr<TH1D>>> Posterior_hist(TotalNumberOfSamples);
1046  // 1.initialisation
1047  for (int sample = 0; sample < TotalNumberOfSamples; ++sample) {
1048  const int nDims = SampleInfo[sample].Dimenstion;
1049  const std::string Sample_Name = SampleInfo[sample].Name;
1050  Posterior_hist[sample] = PerBinHistogram(Toys[sample][0].get(), sample, nDims, suffix);
1051  auto PredictiveHist = M3::Clone(Toys[sample][0].get());
1052  // Clear the bin contents
1053  PredictiveHist->Reset();
1054  PredictiveHist->SetName((Sample_Name + "_" + suffix + "_PostPred").c_str());
1055  PredictiveHist->SetTitle((Sample_Name + "_" + suffix + "_PostPred").c_str());
1056  PredictiveHist->SetDirectory(nullptr);
1057  PostPred[sample] = std::move(PredictiveHist);
1058  }
1059 
1061  #ifdef MULTITHREAD
1062  #pragma omp parallel for
1063  #endif
1064  for (int sample = 0; sample < TotalNumberOfSamples; ++sample) {
1065  const int nDims = SampleInfo[sample].Dimenstion;
1066  auto& hist = Toys[sample][0];
1067  for (size_t iToy = 0; iToy < Toys[sample].size(); ++iToy) {
1068  if(nDims == 2) {
1069  if(std::string(hist->ClassName()) == "TH2Poly") {
1070  for (int i = 1; i <= static_cast<TH2Poly*>(hist.get())->GetNumberOfBins(); ++i) {
1071  double content = Toys[sample][iToy]->GetBinContent(i);
1072  Posterior_hist[sample][i-1]->Fill(content, ReweightWeight[iToy]);
1073  }
1074  } else {
1075  int nbinsx = hist->GetNbinsX();
1076  int nbinsy = hist->GetNbinsY();
1077  for (int iy = 1; iy <= nbinsy; ++iy) {
1078  for (int ix = 1; ix <= nbinsx; ++ix) {
1079  int Bin = (iy-1) * nbinsx + (ix-1);
1080  double content = Toys[sample][iToy]->GetBinContent(ix, iy);
1081  Posterior_hist[sample][Bin]->Fill(content, ReweightWeight[iToy]);
1082  } // end loop over X bins
1083  } // end loop over Y bins
1084  }
1085  } else {
1086  int nbinsx = hist->GetNbinsX();
1087  for (int i = 1; i <= nbinsx; ++i) {
1088  double content = Toys[sample][iToy]->GetBinContent(i);
1089  Posterior_hist[sample][i-1]->Fill(content, ReweightWeight[iToy]);
1090  } // end loop over bins
1091  } // end if over dimensions
1092  } // end loop over toys
1093  } // end loop over samples
1094 
1095  // 3.save
1096  for (int sample = 0; sample < TotalNumberOfSamples; ++sample) {
1097  const int nDims = SampleInfo[sample].Dimenstion;
1098  auto& hist = Toys[sample][0];
1099  Directory[sample]->cd();
1100  if(nDims == 2) {
1101  if(std::string(hist->ClassName()) == "TH2Poly") {
1102  for (int i = 1; i <= static_cast<TH2Poly*>(hist.get())->GetNumberOfBins(); ++i) {
1103  PostPred[sample]->SetBinContent(i, Posterior_hist[sample][i-1]->GetMean());
1104  // KS: If ROOT below 6.18 one need -1 only for error due to stupid bug...
1105  PostPred[sample]->SetBinError(i, Posterior_hist[sample][i-1]->GetRMS());
1106  if (DebugHistograms) Posterior_hist[sample][i-1]->Write();
1107  } // end loop over poly bins
1108  } else {
1109  int nbinsx = hist->GetNbinsX();
1110  int nbinsy = hist->GetNbinsY();
1111  for (int iy = 1; iy <= nbinsy; ++iy) {
1112  for (int ix = 1; ix <= nbinsx; ++ix) {
1113  int Bin = (iy-1) * nbinsx + (ix-1);
1114  if (DebugHistograms) Posterior_hist[sample][Bin]->Write();
1115  PostPred[sample]->SetBinContent(ix, iy, Posterior_hist[sample][Bin]->GetMean());
1116  PostPred[sample]->SetBinError(ix, iy, Posterior_hist[sample][Bin]->GetRMS());
1117  } // end loop over x
1118  } // end loop over y
1119  }
1120  } else {
1121  int nbinsx = hist->GetNbinsX();
1122  for (int i = 1; i <= nbinsx; ++i) {
1123  PostPred[sample]->SetBinContent(i, Posterior_hist[sample][i-1]->GetMean());
1124  PostPred[sample]->SetBinError(i, Posterior_hist[sample][i-1]->GetRMS());
1125  if (DebugHistograms) Posterior_hist[sample][i-1]->Write();
1126  }
1127  }
1128  if(WriteHist) PostPred[sample]->Write();
1129  } // end loop over samples
1130  return PostPred;
1131 }
1132 
1133 // *************************
1134 // Perform predictive analysis
1136 // *************************
1137  // Remove not useful stuff
1138  SanitiseInputs();
1139 
1140  MACH3LOG_INFO("Starting {}", __func__);
1141  MACH3LOG_WARN("\033[0;31mCurrent Total RAM usage is {:.2f} GB\033[0m", M3::Utils::getValue("VmRSS") / 1048576.0);
1142  MACH3LOG_WARN("\033[0;31mOut of Total available RAM {:.2f} GB\033[0m", M3::Utils::getValue("MemTotal") / 1048576.0);
1143 
1144  TStopwatch TempClock;
1145  TempClock.Start();
1146 
1147  auto DebugHistograms = GetFromManager<bool>(fitMan->raw()["Predictive"]["DebugHistograms"], false, __FILE__, __LINE__);
1148  auto doByMode = GetFromManager<bool>(fitMan->raw()["Predictive"]["ByMode"], false, __FILE__, __LINE__);
1149 
1150  TDirectory* PredictiveDir = outputFile->mkdir("Predictive");
1151  std::vector<TDirectory*> SampleDirectories;
1152  SampleDirectories.resize(TotalNumberOfSamples+1);
1153 
1154  // open directory for every sample
1155  for (int sample = 0; sample < TotalNumberOfSamples+1; ++sample) {
1156  SampleDirectories[sample] = PredictiveDir->mkdir(SampleInfo[sample].Name.c_str());
1157  }
1158 
1159  // Produce Violin style spectra
1160  Study1DProjections(SampleDirectories);
1161  // Produce Post pred by each mode individually
1162  if(doByMode) StudyByMode1DProjections(SampleDirectories);
1163  // Produce posterior predictive distribution for mc
1164  auto PostPred_mc = MakePredictive(MC_Hist_Toy, SampleDirectories, "mc", DebugHistograms, false);
1165  // Produce posterior predictive distribution for w2
1166  auto PostPred_w2 = MakePredictive(W2_Hist_Toy, SampleDirectories, "w2", false, false);
1167  // Calculate Posterior Predictive LLH
1168  PredictiveLLH(Data_Hist, PostPred_mc, PostPred_w2, SampleDirectories);
1169  // Calculate Posterior Predictive $p$-value
1170  PosteriorPredictivepValue(PostPred_mc, SampleDirectories);
1171  // Check how number of events changed
1172  RateAnalysis(MC_Hist_Toy, SampleDirectories);
1173 
1174  // Close directories
1175  for (int sample = 0; sample < TotalNumberOfSamples+1; ++sample) {
1176  SampleDirectories[sample]->Close();
1177  delete SampleDirectories[sample];
1178  }
1179 
1180  auto StudyBeta = GetFromManager<bool>(fitMan->raw()["Predictive"]["StudyBetaParameters"], true, __FILE__, __LINE__);
1181  auto StudyInfoCriterion = GetFromManager<bool>(fitMan->raw()["Predictive"]["StudyInformationCriterion"], true, __FILE__, __LINE__);
1182  auto StudyCorr = GetFromManager<bool>(fitMan->raw()["Predictive"]["StudyCorrelations"], true, __FILE__, __LINE__);
1183 
1184  // Studying information criterion
1185  if(StudyInfoCriterion) StudyInformationCriterion(M3::kWAIC, PostPred_mc, PostPred_w2);
1186  // Study Prior/Posterior correlations between samples etc.
1187  if(StudyCorr) StudyCorrelations(PredictiveDir, MC_Hist_Toy, DebugHistograms);
1188  // Perform beta analysis for mc statical uncertainty
1189  if(StudyBeta) StudyBetaParameters(PredictiveDir);
1190 
1191  PredictiveDir->Close();
1192  delete PredictiveDir;
1193 
1194  outputFile->cd();
1195 
1196  TempClock.Stop();
1197  MACH3LOG_INFO("{} took {:.2f}s to finish for {} toys", __func__, TempClock.RealTime(), Ntoys);
1198 }
1199 
1200 // *************************
1201 double PredictiveThrower::CalcLLH(const double data,
1202  const double mc,
1203  const double w2,
1204  const SampleHandlerInterface* SampleHandler) const {
1205 // *************************
1206  double llh = SampleHandler->GetTestStatLLH(data, mc, w2);
1207  //KS: do times 2 because banff reports chi2
1208  return 2*llh;
1209 }
1210 
1211 // *************************
1212 double PredictiveThrower::CalcLLH(const TH1* DatHist,
1213  const TH1* MCHist,
1214  const TH1* W2Hist,
1215  const SampleHandlerInterface* SampleHandler) const {
1216 // *************************
1217  // 1D case
1218  if (auto h1 = dynamic_cast<const TH1D*>(DatHist)) {
1219  return GetLLH(h1,
1220  static_cast<const TH1D*>(MCHist),
1221  static_cast<const TH1D*>(W2Hist),
1222  SampleHandler);
1223  }
1224 
1225  // 2D case
1226  if (auto h2 = dynamic_cast<const TH2D*>(DatHist)) {
1227  return GetLLH(h2,
1228  static_cast<const TH2D*>(MCHist),
1229  static_cast<const TH2D*>(W2Hist),
1230  SampleHandler);
1231  }
1232 
1233  // 2D poly case
1234  if (auto h2p = dynamic_cast<const TH2Poly*>(DatHist)) {
1235  return GetLLH(h2p,
1236  static_cast<const TH2Poly*>(MCHist),
1237  static_cast<const TH2Poly*>(W2Hist),
1238  SampleHandler);
1239  }
1240 
1241  MACH3LOG_ERROR("Unsupported histogram type in {}", __func__);
1242  throw MaCh3Exception(__FILE__ , __LINE__ );
1243 }
1244 
1245 // *************************
1246 double PredictiveThrower::GetLLH(const TH1D* DatHist,
1247  const TH1D* MCHist,
1248  const TH1D* W2Hist,
1249  const SampleHandlerInterface* SampleHandler) const {
1250 // *************************
1251  double llh = 0.0;
1252  for (int i = 1; i <= DatHist->GetXaxis()->GetNbins(); ++i)
1253  {
1254  const double data = DatHist->GetBinContent(i);
1255  const double mc = MCHist->GetBinContent(i);
1256  const double w2 = W2Hist->GetBinContent(i);
1257  llh += SampleHandler->GetTestStatLLH(data, mc, w2);
1258  }
1259  //KS: do times 2 because banff reports chi2
1260  return 2*llh;
1261 }
1262 
1263 // *************************
1264 double PredictiveThrower::GetLLH(const TH2Poly* DatHist,
1265  const TH2Poly* MCHist,
1266  const TH2Poly* W2Hist,
1267  const SampleHandlerInterface* SampleHandler) const {
1268 // *************************
1269  double llh = 0.0;
1270  for (int i = 1; i <= DatHist->GetNumberOfBins(); ++i)
1271  {
1272  const double data = DatHist->GetBinContent(i);
1273  const double mc = MCHist->GetBinContent(i);
1274  const double w2 = W2Hist->GetBinContent(i);
1275  llh += SampleHandler->GetTestStatLLH(data, mc, w2);
1276  }
1277  //KS: do times 2 because banff reports chi2
1278  return 2*llh;
1279 }
1280 
1281 // *************************
1282 double PredictiveThrower::GetLLH(const TH2D* DatHist,
1283  const TH2D* MCHist,
1284  const TH2D* W2Hist,
1285  const SampleHandlerInterface* SampleHandler) const {
1286 // *************************
1287  double llh = 0.0;
1288 
1289  const int nBinsX = DatHist->GetXaxis()->GetNbins();
1290  const int nBinsY = DatHist->GetYaxis()->GetNbins();
1291 
1292  for (int i = 1; i <= nBinsX; ++i)
1293  {
1294  for (int j = 1; j <= nBinsY; ++j)
1295  {
1296  const double data = DatHist->GetBinContent(i, j);
1297  const double mc = MCHist->GetBinContent(i, j);
1298  const double w2 = W2Hist->GetBinContent(i, j);
1299 
1300  llh += SampleHandler->GetTestStatLLH(data, mc, w2);
1301  }
1302  }
1303 
1304  // KS: do times 2 because banff reports chi2
1305  return 2 * llh;
1306 }
1307 
1308 // ****************
1309 //KS: We have two methods how to apply statistical fluctuation standard is faster hence is default
1310 void PredictiveThrower::MakeFluctuatedHistogram(TH1* FluctHist, TH1* Hist) {
1311 // ****************
1312  // Determine which fluctuation function to call
1313  auto applyFluctuation = [&](auto* f, auto* h) {
1314  if (StandardFluctuation) {
1316  } else {
1318  }
1319  };
1320 
1321  if (Hist->InheritsFrom(TH2Poly::Class())) {
1322  applyFluctuation(static_cast<TH2Poly*>(FluctHist), static_cast<TH2Poly*>(Hist));
1323  }
1324  else if (Hist->InheritsFrom(TH2D::Class())) {
1325  applyFluctuation(static_cast<TH2D*>(FluctHist), static_cast<TH2D*>(Hist));
1326  }
1327  else if (Hist->InheritsFrom(TH1D::Class())) {
1328  applyFluctuation(static_cast<TH1D*>(FluctHist), static_cast<TH1D*>(Hist));
1329  }
1330  else {
1331  MACH3LOG_ERROR("Unsupported histogram type");
1332  throw MaCh3Exception(__FILE__ , __LINE__ );
1333  }
1334 }
1335 
1336 // *************************
1337 void PredictiveThrower::PosteriorPredictivepValue(const std::vector<std::unique_ptr<TH1>>& PostPred_mc,
1338  const std::vector<TDirectory*>& SampleDir) {
1339 // *************************
1340  // Step 1: Initialize per-toy accumulators once
1341  // [Sample] [Toys]
1342  auto make_matrix = [&](double init = 0.0) {
1343  return std::vector<std::vector<double>>(
1345  std::vector<double>(Ntoys, init));
1346  };
1347  auto chi2_dat = make_matrix();
1348  auto chi2_mc = make_matrix();
1349  auto chi2_pred = make_matrix();
1350  auto chi2_rate_dat = make_matrix();
1351  auto chi2_rate_mc = make_matrix();
1352  auto chi2_rate_pred = make_matrix();
1353 
1354  // 2. Add penalty terms to global bin
1355  for (int iToy = 0; iToy < Ntoys; ++iToy) {
1356  chi2_dat[TotalNumberOfSamples][iToy] = PenaltyTerm[iToy];
1357  chi2_mc[TotalNumberOfSamples][iToy] = PenaltyTerm[iToy];
1358  chi2_pred[TotalNumberOfSamples][iToy] = PenaltyTerm[iToy];
1359 
1360  chi2_rate_dat[TotalNumberOfSamples][iToy] = PenaltyTerm[iToy];
1361  chi2_rate_mc[TotalNumberOfSamples][iToy] = PenaltyTerm[iToy];
1362  chi2_rate_pred[TotalNumberOfSamples][iToy] = PenaltyTerm[iToy];
1363  }
1364 
1366  for (int iSample = 0; iSample < TotalNumberOfSamples; ++iSample) {
1367  auto SampleHandler = SampleInfo[iSample].SamHandler;
1368  for (int iToy = 0; iToy < Ntoys; ++iToy) {
1369  // Clone histograms to avoid modifying originals
1370  auto DrawFluctHist = M3::Clone(MC_Hist_Toy[iSample][iToy].get());
1371  auto PredFluctHist = M3::Clone(PostPred_mc[iSample].get());
1372 
1373  // Apply fluctuations
1374  MakeFluctuatedHistogram(DrawFluctHist.get(), MC_Hist_Toy[iSample][iToy].get());
1375  MakeFluctuatedHistogram(PredFluctHist.get(), PostPred_mc[iSample].get());
1376 
1377  // I. SHAPE + RATE (bin-by-bin likelihood)
1378  chi2_dat[iSample][iToy] = CalcLLH(Data_Hist[iSample].get(), MC_Hist_Toy[iSample][iToy].get(), W2_Hist_Toy[iSample][iToy].get(), SampleHandler);
1379  chi2_mc[iSample][iToy] = CalcLLH(DrawFluctHist.get(), MC_Hist_Toy[iSample][iToy].get(), W2_Hist_Toy[iSample][iToy].get(), SampleHandler);
1380  chi2_pred[iSample][iToy] = CalcLLH(PredFluctHist.get(), MC_Hist_Toy[iSample][iToy].get(), W2_Hist_Toy[iSample][iToy].get(), SampleHandler);
1381 
1382  // II. RATE-ONLY (total normalization)
1383  chi2_rate_dat[iSample][iToy] = CalcLLH(Data_Hist[iSample]->Integral(), MC_Hist_Toy[iSample][iToy]->Integral(), W2_Hist_Toy[iSample][iToy]->Integral(), SampleHandler);
1384  chi2_rate_mc[iSample][iToy] = CalcLLH(DrawFluctHist->Integral(), MC_Hist_Toy[iSample][iToy]->Integral(), W2_Hist_Toy[iSample][iToy]->Integral(), SampleHandler);
1385  chi2_rate_pred[iSample][iToy] = CalcLLH(PredFluctHist->Integral(), MC_Hist_Toy[iSample][iToy]->Integral(), W2_Hist_Toy[iSample][iToy]->Integral(), SampleHandler);
1386 
1387  // III. accumulate global sums ---
1388  chi2_dat[TotalNumberOfSamples][iToy] += chi2_dat[iSample][iToy];
1389  chi2_mc[TotalNumberOfSamples][iToy] += chi2_mc[iSample][iToy];
1390  chi2_pred[TotalNumberOfSamples][iToy] += chi2_pred[iSample][iToy];
1391 
1392  chi2_rate_dat[TotalNumberOfSamples][iToy] += chi2_rate_dat[iSample][iToy];
1393  chi2_rate_mc[TotalNumberOfSamples][iToy] += chi2_rate_mc[iSample][iToy];
1394  chi2_rate_pred[TotalNumberOfSamples][iToy] += chi2_rate_pred[iSample][iToy];
1395  }
1396  }
1397 
1398  // 4. Produce pValue plots
1399  // Shape+rate posterior predictive checks
1400  MakeChi2Plots(chi2_mc, "-2LLH (Draw Fluc, Draw)", chi2_dat, "-2LLH (Data, Draw)", SampleDir, "_drawfluc_draw");
1401  MakeChi2Plots(chi2_pred, "-2LLH (Pred Fluc, Draw)", chi2_dat, "-2LLH (Data, Draw)", SampleDir, "_predfluc_draw");
1402 
1403  // Rate-only posterior predictive checks
1404  MakeChi2Plots(chi2_rate_mc, "-2LLH (Rate Draw Fluc, Draw)", chi2_rate_dat, "-2LLH (Rate Data, Draw)", SampleDir, "_rate_drawfluc_draw");
1405  MakeChi2Plots(chi2_rate_pred, "-2LLH (Rate Pred Fluc, Draw)", chi2_rate_dat, "-2LLH (Rate Data, Draw)", SampleDir, "_rate_predfluc_draw");
1406 }
1407 
1408 // *************************
1409 void PredictiveThrower::PredictiveLLH(const std::vector<std::unique_ptr<TH1>>& Data_histogram,
1410  const std::vector<std::unique_ptr<TH1>>& PostPred_mc,
1411  const std::vector<std::unique_ptr<TH1>>& PostPred_w,
1412  const std::vector<TDirectory*>& SampleDir) {
1413 // *************************
1414  MACH3LOG_INFO("{:<55} {:<10} {:<10} {:<10}", "Sample", "DataInt", "MCInt", "-2LLH");
1415  MACH3LOG_INFO("{:-<55} {:-<10} {:-<10} {:-<10}", "", "", "", "");
1416  for (int iSample = 0; iSample < TotalNumberOfSamples; ++iSample) {
1417  SampleDir[iSample]->cd();
1418  ExtractLLH(Data_histogram[iSample].get(), PostPred_mc[iSample].get(), PostPred_w[iSample].get(), SampleInfo[iSample].SamHandler);
1419  PostPred_mc[iSample]->Write();
1420  }
1421 }
1422 
1423 
1424 // *************************
1425 void PredictiveThrower::MakeChi2Plots(const std::vector<std::vector<double>>& Chi2_x,
1426  const std::string& Chi2_x_title,
1427  const std::vector<std::vector<double>>& Chi2_y,
1428  const std::string& Chi2_y_title,
1429  const std::vector<TDirectory*>& SampleDir,
1430  const std::string Title) {
1431 // *************************
1432  for (int iSample = 0; iSample < TotalNumberOfSamples+1; ++iSample) {
1433  SampleDir[iSample]->cd();
1434 
1435  // Transpose to extract chi2 values for a given sample across all toys
1436  std::vector<double> chi2_y_sample(Ntoys);
1437  std::vector<double> chi2_x_per_sample(Ntoys);
1438 
1439  for (int iToy = 0; iToy < Ntoys; ++iToy) {
1440  chi2_y_sample[iToy] = Chi2_y[iSample][iToy];
1441  chi2_x_per_sample[iToy] = Chi2_x[iSample][iToy];
1442  }
1443 
1444  const double min_val = std::min(*std::min_element(chi2_y_sample.begin(), chi2_y_sample.end()),
1445  *std::min_element(chi2_x_per_sample.begin(), chi2_x_per_sample.end()));
1446  const double max_val = std::max(*std::max_element(chi2_y_sample.begin(), chi2_y_sample.end()),
1447  *std::max_element(chi2_x_per_sample.begin(), chi2_x_per_sample.end()));
1448 
1449  auto chi2_hist = std::make_unique<TH2D>((SampleInfo[iSample].Name+ Title).c_str(),
1450  (SampleInfo[iSample].Name+ Title).c_str(),
1451  50, min_val, max_val, 50, min_val, max_val);
1452  chi2_hist->SetDirectory(nullptr);
1453  chi2_hist->GetXaxis()->SetTitle(Chi2_x_title.c_str());
1454  chi2_hist->GetYaxis()->SetTitle(Chi2_y_title.c_str());
1455 
1456  for (int iToy = 0; iToy < Ntoys; ++iToy) {
1457  chi2_hist->Fill(chi2_x_per_sample[iToy], chi2_y_sample[iToy]);
1458  }
1459 
1460  Get2DBayesianpValue(chi2_hist.get());
1461  chi2_hist->Write();
1462  }
1463 }
1464 
1465 // *************************
1466 // Study Beta Parameters
1467 void PredictiveThrower::StudyBetaParameters(TDirectory* PredictiveDir) {
1468 // *************************
1469  bool StudyBeta = GetFromManager<bool>(fitMan->raw()["Predictive"]["StudyBetaParameters"], true, __FILE__, __LINE__ );
1470  if (StudyBeta == false) return;
1471 
1472  MACH3LOG_INFO("Starting {}", __func__);
1473  TDirectory* BetaDir = PredictiveDir->mkdir("BetaParameters");
1474  std::vector<std::vector<std::unique_ptr<TH1D>>> BetaHist(TotalNumberOfSamples);
1475  std::vector<TDirectory *> DirBeta(TotalNumberOfSamples);
1476  // initialise directory for each sample
1477  for (int sample = 0; sample < TotalNumberOfSamples; ++sample) {
1478  BetaDir->cd();
1479  DirBeta[sample] = BetaDir->mkdir(SampleInfo[sample].Name.c_str());
1480  }
1481 
1483  for (int iSample = 0; iSample < TotalNumberOfSamples; ++iSample) {
1484  const int nDims = SampleInfo[iSample].Dimenstion;
1485  // Use any histogram that defines the binning structure
1486  TH1* RefHist = Data_Hist[iSample].get();
1487  BetaHist[iSample] = PerBinHistogram(RefHist, iSample, nDims, "Beta_Parameter");
1488  // Change x-axis title
1489  for (size_t i = 0; i < BetaHist[iSample].size(); ++i) {
1490  BetaHist[iSample][i]->GetXaxis()->SetTitle("beta parameter");
1491  }
1492  }
1493 
1495  #ifdef MULTITHREAD
1496  #pragma omp parallel for
1497  #endif
1498  for (int iSample = 0; iSample < TotalNumberOfSamples; ++iSample) {
1499  const int nDims = SampleInfo[iSample].Dimenstion;
1500  const auto likelihood = SampleInfo[iSample].SamHandler->GetTestStatistic();
1501  for (int iToy = 0; iToy < Ntoys; ++iToy) {
1502  if (nDims == 2) {
1503  if(std::string(Data_Hist[iSample]->ClassName()) == "TH2Poly") {
1504  for (int i = 1; i <= static_cast<TH2Poly*>(Data_Hist[iSample].get())->GetNumberOfBins(); ++i) {
1505  const double Data = Data_Hist[iSample]->GetBinContent(i);
1506  const double MC = MC_Hist_Toy[iSample][iToy]->GetBinContent(i);
1507  const double w2 = W2_Hist_Toy[iSample][iToy]->GetBinContent(i);
1508 
1509  const double BetaParam = GetBetaParameter(Data, MC, w2, likelihood);
1510  BetaHist[iSample][i-1]->Fill(BetaParam, ReweightWeight[iToy]);
1511  } // end loop over poly bins
1512  } else {
1513  const int nX = Data_Hist[iSample]->GetNbinsX();
1514  const int nY = Data_Hist[iSample]->GetNbinsY();
1515  for (int iy = 1; iy <= nY; ++iy) {
1516  for (int ix = 1; ix <= nX; ++ix) {
1517  const int FlatBin = (iy-1) * nX + (ix-1);
1518 
1519  const double Data = Data_Hist[iSample]->GetBinContent(ix, iy);
1520  const double MC = MC_Hist_Toy[iSample][iToy]->GetBinContent(ix, iy);
1521  const double w2 = W2_Hist_Toy[iSample][iToy]->GetBinContent(ix, iy);
1522 
1523  const double BetaParam = GetBetaParameter(Data, MC, w2, likelihood);
1524  BetaHist[iSample][FlatBin]->Fill(BetaParam, ReweightWeight[iToy]);
1525  }
1526  } // end loop over x
1527  } // end loop over y
1528  } else {
1529  int nbinsx = Data_Hist[iSample]->GetNbinsX();
1530  for (int ix = 1; ix <= nbinsx; ++ix) {
1532  const double Data = Data_Hist[iSample]->GetBinContent(ix);
1533  const double MC = MC_Hist_Toy[iSample][iToy]->GetBinContent(ix);
1534  const double w2 = W2_Hist_Toy[iSample][iToy]->GetBinContent(ix);
1535 
1536  const double BetaParam = GetBetaParameter(Data, MC, w2, likelihood);
1537  BetaHist[iSample][ix-1]->Fill(BetaParam, ReweightWeight[iToy]);
1538  } // end loop over bins
1539  }
1540  } // end loop over toys
1541  } // end loop over samples
1542 
1544  for (int iSample = 0; iSample < TotalNumberOfSamples; ++iSample) {
1545  for (size_t iBin = 0; iBin < BetaHist[iSample].size(); iBin++) {
1546  DirBeta[iSample]->cd();
1547  BetaHist[iSample][iBin]->Write();
1548  }
1549  DirBeta[iSample]->Close();
1550  delete DirBeta[iSample];
1551  }
1552  BetaDir->Close();
1553  delete BetaDir;
1554 
1555  PredictiveDir->cd();
1556 }
1557 
1558 // ****************
1559 // Study Prior/Posterior correlations between samples etc.
1560 void PredictiveThrower::StudyCorrelations(TDirectory* PredictiveDir,
1561  const std::vector<std::vector<std::unique_ptr<TH1>>>& Toys,
1562  const bool DebugHistograms) const {
1563 // ****************
1564  MACH3LOG_INFO("Startin {}", __func__);
1565 
1566  // Make a new directory
1567  TDirectory *CorrDir = PredictiveDir->mkdir("Correlations");
1568  CorrDir->cd();
1569 
1570  std::vector<double> minVals(TotalNumberOfSamples, std::numeric_limits<double>::max());
1571  std::vector<double> maxVals(TotalNumberOfSamples, std::numeric_limits<double>::lowest());
1572  #ifdef MULTITHREAD
1573  #pragma omp parallel for
1574  #endif
1575  for (int i = 0; i < TotalNumberOfSamples; ++i)
1576  {
1577  for (const auto& toyHist : Toys[i])
1578  {
1579  const double val = toyHist->Integral();
1580  if (val < minVals[i]) minVals[i] = val;
1581  if (val > maxVals[i]) maxVals[i] = val;
1582  }
1583  }
1584  auto hSamCorr = std::make_unique<TH2D>("Sample Correlation", "Sample Correlation", TotalNumberOfSamples, 0,
1586  hSamCorr->SetDirectory(nullptr);
1587  hSamCorr->GetZaxis()->SetTitle("Correlation");
1588  hSamCorr->SetMinimum(-1);
1589  hSamCorr->SetMaximum(1);
1590  hSamCorr->GetXaxis()->SetLabelSize(0.015);
1591  hSamCorr->GetYaxis()->SetLabelSize(0.015);
1592  // Loop over the Covariance matrix entries
1593  for (int i = 0; i < TotalNumberOfSamples; ++i) {
1594  hSamCorr->SetBinContent(i+1, i+1, 1.0);
1595  hSamCorr->GetXaxis()->SetBinLabel(i+1, SampleInfo[i].Name.c_str());
1596  for (int j = 0; j < TotalNumberOfSamples; ++j) {
1597  hSamCorr->GetYaxis()->SetBinLabel(j+1, SampleInfo[j].Name.c_str());
1598  }
1599  }
1600 
1601  std::vector<std::vector<std::unique_ptr<TH2D>>> SamCorr(TotalNumberOfSamples);
1602  for (int i = 0; i < TotalNumberOfSamples; ++i)
1603  {
1604  SamCorr[i].resize(TotalNumberOfSamples);
1605  const double Min_i = minVals[i];
1606  const double Max_i = maxVals[i];
1607  for (int j = 0; j < TotalNumberOfSamples; ++j)
1608  {
1609  const double Min_j = minVals[j];
1610  const double Max_j = maxVals[j];
1611  // TH2D to hold the Correlation
1612  std::string name = "SamCorr_" + std::to_string(i) + "_" + std::to_string(j);
1613  SamCorr[i][j] = std::make_unique<TH2D>(name.c_str(), name.c_str(), 70, Min_i, Max_i, 70, Min_j, Max_j);
1614  SamCorr[i][j]->SetDirectory(nullptr);
1615  SamCorr[i][j]->SetMinimum(0);
1616  SamCorr[i][j]->GetXaxis()->SetTitle(SampleInfo[i].Name.c_str());
1617  SamCorr[i][j]->GetYaxis()->SetTitle(SampleInfo[j].Name.c_str());
1618  SamCorr[i][j]->GetZaxis()->SetTitle("Events");
1619  }
1620  }
1621 
1622  // Now we are sure we have the diagonal elements, let's make the off-diagonals
1623  #ifdef MULTITHREAD
1624  #pragma omp parallel for
1625  #endif
1626  for (int i = 0; i < TotalNumberOfSamples; ++i)
1627  {
1628  for (int j = 0; j <= i; ++j)
1629  {
1630  // Skip the diagonal elements which we've already done above
1631  if (j == i) continue;
1632 
1633  for (int iToy = 0; iToy < Ntoys; ++iToy)
1634  {
1635  SamCorr[i][j]->Fill(Toys[i][iToy]->Integral(), Toys[j][iToy]->Integral());
1636  }
1637  SamCorr[i][j]->Smooth();
1638 
1639  // The value of the Covariance
1640  const double corr = SamCorr[i][j]->GetCorrelationFactor();
1641  hSamCorr->SetBinContent(i+1, j+1, corr);
1642  hSamCorr->SetBinContent(j+1, i+1, corr);
1643  }// End j loop
1644  }// End i loop
1645 
1646  hSamCorr->Draw("colz");
1647  hSamCorr->Write("Sample_Corr");
1648 
1649  if(DebugHistograms) {
1650  for (int i = 0; i < TotalNumberOfSamples; ++i){
1651  for (int j = 0; j <= i; ++j) {
1652  // Skip the diagonal elements which we've already done above
1653  if (j == i) continue;
1654  SamCorr[i][j]->Write();
1655  }// End j loop
1656  }// End i loop
1657  } // end if debugHist
1658 
1659  PredictiveDir->cd();
1660 }
1661 
1662 // ****************
1663 // Calculate the LLH for TH1, set the LLH to title of MCHist
1664 void PredictiveThrower::ExtractLLH(TH1* DatHist, TH1* MCHist, TH1* W2Hist, const SampleHandlerInterface* SampleHandler) const {
1665 // ****************
1666  const double llh = CalcLLH(DatHist, MCHist, W2Hist, SampleHandler);
1667  std::stringstream ss;
1668  ss << "_2LLH=" << llh;
1669  MCHist->SetTitle((std::string(MCHist->GetTitle())+ss.str()).c_str());
1670  MACH3LOG_INFO("{:<55} {:<10.2f} {:<10.2f} {:<10.2f}", MCHist->GetName(), DatHist->Integral(), MCHist->Integral(), llh);
1671 }
1672 
1673 // ****************
1674 // Make the 1D Event Rate Hist
1675 void PredictiveThrower::MakeCutEventRate(TH1D *Histogram, const double DataRate) const {
1676 // ****************
1677  // Open the ROOT file
1678  int originalErrorWarning = gErrorIgnoreLevel;
1679  gErrorIgnoreLevel = kFatal;
1680 
1681  // For the event rate histogram add a TLine to the data rate
1682  auto TempLine = std::make_unique<TLine>(DataRate, Histogram->GetMinimum(), DataRate, Histogram->GetMaximum());
1683  TempLine->SetLineColor(kRed);
1684  TempLine->SetLineWidth(2);
1685  // Also fit a Gaussian because why not?
1686  auto Fitter = std::make_unique<TF1>("Fit", "gaus", Histogram->GetBinLowEdge(1), Histogram->GetBinLowEdge(Histogram->GetNbinsX()+1));
1687  Histogram->Fit(Fitter.get(), "RQ");
1688  Fitter->SetLineColor(kRed-5);
1689  // Calculate a p-value
1690  double Above = 0.0;
1691  for (int z = 0; z < Histogram->GetNbinsX(); ++z) {
1692  const double xvalue = Histogram->GetBinCenter(z+1);
1693  if (xvalue >= DataRate) {
1694  Above += Histogram->GetBinContent(z+1);
1695  }
1696  }
1697  const double pvalue = Above/Histogram->Integral();
1698  TLegend Legend(0.4, 0.75, 0.98, 0.90);
1699  Legend.SetFillColor(0);
1700  Legend.SetFillStyle(0);
1701  Legend.SetLineWidth(0);
1702  Legend.SetLineColor(0);
1703  Legend.AddEntry(TempLine.get(), Form("Data, %.0f, p-value=%.2f", DataRate, pvalue), "l");
1704  Legend.AddEntry(Histogram, Form("MC, #mu=%.1f#pm%.1f", Histogram->GetMean(), Histogram->GetRMS()), "l");
1705  Legend.AddEntry(Fitter.get(), Form("Gauss, #mu=%.1f#pm%.1f", Fitter->GetParameter(1), Fitter->GetParameter(2)), "l");
1706  std::string TempTitle = std::string(Histogram->GetName());
1707  TempTitle += "_canv";
1708  TCanvas TempCanvas(TempTitle.c_str(), TempTitle.c_str(), 1024, 1024);
1709  TempCanvas.SetGridx();
1710  TempCanvas.SetGridy();
1711  TempCanvas.SetRightMargin(0.03);
1712  TempCanvas.SetBottomMargin(0.08);
1713  TempCanvas.SetLeftMargin(0.10);
1714  TempCanvas.SetTopMargin(0.06);
1715  TempCanvas.cd();
1716  Histogram->Draw();
1717  TempLine->Draw("same");
1718  Fitter->Draw("same");
1719  Legend.Draw("same");
1720  TempCanvas.Write();
1721  Histogram->Write();
1722  gErrorIgnoreLevel = originalErrorWarning;
1723 }
1724 
1725 // *************************
1726 void PredictiveThrower::RateAnalysis(const std::vector<std::vector<std::unique_ptr<TH1>>>& Toys,
1727  const std::vector<TDirectory*>& SampleDirectories) const {
1728 // *************************
1729  std::vector<std::unique_ptr<TH1D>> EventHist(TotalNumberOfSamples+1);
1730  for (int iSample = 0; iSample < TotalNumberOfSamples+1; ++iSample) {
1731  std::string Title = "EventHist: ";
1732  if (iSample == TotalNumberOfSamples) {
1733  Title = "Total";
1734  } else {
1735  Title = SampleInfo[iSample].Name;
1736  }
1737  Title += "_sum";
1738  //KS: When a histogram is created with an axis lower limit greater or equal to its upper limit ROOT will automatically adjust histogram range
1739  // https://root.cern.ch/doc/master/classTH1.html#auto-bin
1740  EventHist[iSample] = std::make_unique<TH1D>(Title.c_str(), Title.c_str(), 100, 1, -1);
1741  EventHist[iSample]->SetDirectory(nullptr);
1742  EventHist[iSample]->GetXaxis()->SetTitle("Total event rate");
1743  EventHist[iSample]->GetYaxis()->SetTitle("Counts");
1744  EventHist[iSample]->SetLineWidth(2);
1745  }
1746 
1747  // First fill per-sample histograms
1748  #ifdef MULTITHREAD
1749  #pragma omp parallel for
1750  #endif
1751  for (int iSample = 0; iSample < TotalNumberOfSamples; ++iSample) {
1752  for (int iToy = 0; iToy < Ntoys; ++iToy) {
1753  double Count = Toys[iSample][iToy]->Integral();
1754  EventHist[iSample]->Fill(Count);
1755  }
1756  }
1757 
1758  // Now fill total histogram properly (per toy)
1759  for (int iToy = 0; iToy < Ntoys; ++iToy) {
1760  double TotalCount = 0.0;
1761  for (int iSample = 0; iSample < TotalNumberOfSamples; ++iSample) {
1762  TotalCount += Toys[iSample][iToy]->Integral();
1763  }
1764  EventHist[TotalNumberOfSamples]->Fill(TotalCount);
1765  }
1766 
1767  double DataRate = 0.0;
1768  std::vector<double> DataRates(TotalNumberOfSamples+1);
1769  #ifdef MULTITHREAD
1770  #pragma omp parallel for reduction(+:DataRate)
1771  #endif
1772  for (int i = 0; i < TotalNumberOfSamples; ++i) {
1773  DataRates[i] = Data_Hist[i]->Integral();
1774  DataRate += DataRates[i];
1775  }
1776  DataRates[TotalNumberOfSamples] = DataRate;
1777 
1778  for (int SampleNum = 0; SampleNum < TotalNumberOfSamples+1; ++SampleNum) {
1779  SampleDirectories[SampleNum]->cd();
1780  //Make fancy event rate histogram
1781  MakeCutEventRate(EventHist[SampleNum].get(), DataRates[SampleNum]);
1782  }
1783 }
1784 
1785 
1786 // ****************
1788  const std::vector<std::unique_ptr<TH1>>& PostPred_mc,
1789  const std::vector<std::unique_ptr<TH1>>& PostPred_w) {
1790 // ****************
1791  MACH3LOG_INFO("******************************");
1792  switch(Criterion) {
1793  case M3::kInfCrit::kBIC:
1794  // Study Bayesian Information Criterion
1795  StudyBIC(PostPred_mc, PostPred_w);
1796  break;
1797  case M3::kInfCrit::kDIC:
1798  // Study Deviance Information Criterion
1799  StudyDIC(PostPred_mc, PostPred_w);
1800  break;
1801  case M3::kInfCrit::kWAIC:
1802  // Study Watanabe-Akaike information criterion (WAIC)
1803  StudyWAIC();
1804  break;
1806  MACH3LOG_ERROR("kInfCrits is not a valid kInfCrit!");
1807  throw MaCh3Exception(__FILE__, __LINE__);
1808  default:
1809  MACH3LOG_ERROR("UNKNOWN Information Criterion SPECIFIED!");
1810  MACH3LOG_ERROR("You gave {}", static_cast<int>(Criterion));
1811  throw MaCh3Exception(__FILE__ , __LINE__ );
1812  }
1813  MACH3LOG_INFO("******************************");
1814 }
1815 
1816 // ****************
1817 void PredictiveThrower::StudyBIC(const std::vector<std::unique_ptr<TH1>>& PostPred_mc,
1818  const std::vector<std::unique_ptr<TH1>>& PostPred_w) {
1819 // ****************
1820  //make fancy event rate histogram
1821  double DataRate = 0.0;
1822  double BinsRate = 0.0;
1823  double TotalLLH = 0.0;
1824  #ifdef MULTITHREAD
1825  #pragma omp parallel for reduction(+:DataRate, BinsRate, TotalLLH)
1826  #endif
1827  for (int i = 0; i < TotalNumberOfSamples; ++i)
1828  {
1829  auto SampleHandler = SampleInfo[i].SamHandler;
1830  auto* h = Data_Hist[i].get();
1831  DataRate += h->Integral();
1832  if (auto h1 = dynamic_cast<TH1D*>(h)) {
1833  BinsRate += h1->GetNbinsX();
1834  } else if (auto h2 = dynamic_cast<TH2D*>(h)) {
1835  BinsRate += h2->GetNbinsX() * h2->GetNbinsY();
1836  } else if (auto h2poly = dynamic_cast<TH2Poly*>(h)) {
1837  BinsRate += h2poly->GetNumberOfBins();
1838  } else {
1839  MACH3LOG_WARN("Unknown histogram type in DataHist[{}]", i);
1840  }
1841  TotalLLH += CalcLLH(Data_Hist[i].get(), PostPred_mc[i].get(), PostPred_w[i].get(), SampleHandler);
1842  }
1843 
1844  const double EventRateBIC = GetBIC(TotalLLH, DataRate, NModelParams);
1845  const double BinBasedBIC = GetBIC(TotalLLH, BinsRate, NModelParams);
1846  MACH3LOG_INFO("Calculated Bayesian Information Criterion using global number of events: {:.2f}", EventRateBIC);
1847  MACH3LOG_INFO("Calculated Bayesian Information Criterion using global number of bins: {:.2f}", BinBasedBIC);
1848  MACH3LOG_INFO("Additional info: NModelParams: {}, DataRate: {:.2f}, BinsRate: {:.2f}", NModelParams, DataRate, BinsRate);
1849 }
1850 
1851 // ****************
1852 // Get the Deviance Information Criterion (DIC)
1853 void PredictiveThrower::StudyDIC(const std::vector<std::unique_ptr<TH1>>& PostPred_mc,
1854  const std::vector<std::unique_ptr<TH1>>& PostPred_w) {
1855 // ****************
1856  //The posterior mean of the deviance
1857  double Dbar = 0.;
1858  double TotalLLH = 0.0;
1859 
1860  #ifdef MULTITHREAD
1861  #pragma omp parallel for reduction(+:Dbar)
1862  #endif
1863  for (int iSample = 0; iSample < TotalNumberOfSamples; ++iSample)
1864  {
1865  auto SampleHandler = SampleInfo[iSample].SamHandler;
1866  TotalLLH += CalcLLH(Data_Hist[iSample].get(), PostPred_mc[iSample].get(), PostPred_w[iSample].get(), SampleHandler);
1867  double LLH_temp = 0.;
1868  for (int iToy = 0; iToy < Ntoys; ++iToy)
1869  {
1870  LLH_temp += CalcLLH(Data_Hist[iSample].get(), MC_Hist_Toy[iSample][iToy].get(), W2_Hist_Toy[iSample][iToy].get(), SampleHandler);
1871  }
1872  Dbar += LLH_temp;
1873  }
1874  Dbar = Dbar / Ntoys;
1875 
1876  // A point estimate of the deviance
1877  const double Dhat = TotalLLH;
1878 
1879  //Effective number of parameters
1880  const double p_D = std::fabs(Dbar - Dhat);
1881 
1882  //Actual test stat
1883  const double DIC_stat = Dhat + 2 * p_D;
1884  MACH3LOG_INFO("Effective number of parameters following DIC formalism is equal to: {:.2f}", p_D);
1885  MACH3LOG_INFO("DIC test statistic = {:.2f}", DIC_stat);
1886 }
1887 
1888 
1889 // ****************
1890 // Helper: update WAIC accumulators for a single toy/bin
1891 void AccumulateWAICToy(const double neg_LLH_temp,
1892  double& mean_llh,
1893  double& mean_llh_squared,
1894  double& sum_exp_llh) {
1895 // ****************
1896  // Negate the negative log-likelihood to get the actual log-likelihood
1897  double LLH_temp = -neg_LLH_temp;
1898 
1899  mean_llh += LLH_temp;
1900  mean_llh_squared += LLH_temp * LLH_temp;
1901  sum_exp_llh += std::exp(LLH_temp);
1902 }
1903 
1904 // ****************
1905 // Helper function to finalize WAIC contributions for one bin
1906 void AccumulateWAICBin(double& mean_llh, double& mean_llh_squared, double& sum_exp_llh,
1907  const unsigned int Ntoys, double& lppd, double& p_WAIC) {
1908 // ****************
1909  // Compute the mean log-likelihood and the squared mean
1910  mean_llh /= Ntoys;
1911  mean_llh_squared /= Ntoys;
1912  sum_exp_llh /= Ntoys;
1913  sum_exp_llh = std::log(sum_exp_llh);
1914 
1915  // Log pointwise predictive density based on Eq. 4 in Gelman2014
1916  lppd += sum_exp_llh;
1917 
1918  // Compute the effective number of parameters for WAIC
1919  p_WAIC += mean_llh_squared - (mean_llh * mean_llh);
1920 }
1921 
1922 // ****************
1923 // Get the Watanabe-Akaike information criterion (WAIC)
1925 // ****************
1926  // log pointwise predictive density
1927  double lppd = 0.;
1928  // effective number of parameters
1929  double p_WAIC = 0.;
1930 
1931  #ifdef MULTITHREAD
1932  #pragma omp parallel for reduction(+:lppd, p_WAIC)
1933  #endif
1934  for (int iSample = 0; iSample < TotalNumberOfSamples; ++iSample) {
1935  auto SampleHandler = SampleInfo[iSample].SamHandler;
1936  auto* hData = Data_Hist[iSample].get();
1937 
1938  if (auto h2poly = dynamic_cast<TH2Poly*>(hData)) {
1939  // TH2Poly: irregular bins, linear indexing
1940  for (int i = 1; i <= h2poly->GetNumberOfBins(); ++i) {
1941  const double data = Data_Hist[iSample]->GetBinContent(i);
1942  double mean_llh = 0.;
1943  double sum_exp_llh = 0;
1944  double mean_llh_squared = 0.;
1945 
1946  for (int iToy = 0; iToy < Ntoys; ++iToy) {
1947  const double mc = MC_Hist_Toy[iSample][iToy]->GetBinContent(i);
1948  const double w2 = W2_Hist_Toy[iSample][iToy]->GetBinContent(i);
1949  // Get the -log-likelihood for this sample and bin
1950  double neg_LLH_temp = SampleHandler->GetTestStatLLH(data, mc, w2);
1951  AccumulateWAICToy(neg_LLH_temp, mean_llh, mean_llh_squared, sum_exp_llh);
1952  }
1953  AccumulateWAICBin(mean_llh, mean_llh_squared, sum_exp_llh, Ntoys, lppd, p_WAIC);
1954  }
1955  } else if (auto h2 = dynamic_cast<TH2D*>(hData)) {
1956  // TH2D: nested loops over X and Y
1957  for (int ix = 1; ix <= h2->GetNbinsX(); ++ix) {
1958  for (int iy = 1; iy <= h2->GetNbinsY(); ++iy) {
1959  const double data = hData->GetBinContent(ix, iy);
1960  double mean_llh = 0.;
1961  double mean_llh_squared = 0.;
1962  double sum_exp_llh = 0.;
1963  for (int iToy = 0; iToy < Ntoys; ++iToy) {
1964  const double mc = MC_Hist_Toy[iSample][iToy]->GetBinContent(ix, iy);
1965  const double w2 = W2_Hist_Toy[iSample][iToy]->GetBinContent(ix, iy);
1966  // Get the -log-likelihood for this sample and bin
1967  double neg_LLH_temp = SampleHandler->GetTestStatLLH(data, mc, w2);
1968  AccumulateWAICToy(neg_LLH_temp, mean_llh, mean_llh_squared, sum_exp_llh);
1969  }
1970  AccumulateWAICBin(mean_llh, mean_llh_squared, sum_exp_llh, Ntoys, lppd, p_WAIC);
1971  }
1972  }
1973  } else if (auto h1 = dynamic_cast<TH1D*>(hData)) {
1974  // TH1D: 1D histogram
1975  for (int iBin = 1; iBin <= h1->GetNbinsX(); ++iBin) {
1976  const double data = hData->GetBinContent(iBin);
1977  double mean_llh = 0.;
1978  double mean_llh_squared = 0.;
1979  double sum_exp_llh = 0.;
1980  for (int iToy = 0; iToy < Ntoys; ++iToy) {
1981  const double mc = MC_Hist_Toy[iSample][iToy]->GetBinContent(iBin);
1982  const double w2 = W2_Hist_Toy[iSample][iToy]->GetBinContent(iBin);
1983 
1984  // Get the -log-likelihood for this sample and bin
1985  double neg_LLH_temp = SampleHandler->GetTestStatLLH(data, mc, w2);
1986  AccumulateWAICToy(neg_LLH_temp, mean_llh, mean_llh_squared, sum_exp_llh);
1987  }
1988  AccumulateWAICBin(mean_llh, mean_llh_squared, sum_exp_llh, Ntoys, lppd, p_WAIC);
1989  }
1990  }
1991  }
1992 
1993  // Compute WAIC, see Eq. 13 in Gelman2014
1994  double WAIC = -2 * (lppd - p_WAIC);
1995  MACH3LOG_INFO("Effective number of parameters following WAIC formalism is equal to: {:.2f}", p_WAIC);
1996  MACH3LOG_INFO("WAIC = {:.2f}", WAIC);
1997 }
void MakeFluctuatedHistogramAlternative(TH1D *FluctHist, TH1D *PolyHist, TRandom3 *rand)
Make Poisson fluctuation of TH1D hist using slow method which is only for cross-check.
void MakeFluctuatedHistogramStandard(TH1D *FluctHist, TH1D *PolyHist, TRandom3 *rand)
Make Poisson fluctuation of TH1D hist using default fast method.
TH1D * PolyProjectionX(TObject *poly, const std::string &TempName, const std::vector< double > &xbins, const bool computeErrors)
WP: Poly Projectors.
void FastViolinFill(TH2D *violin, TH1D *hist_1d)
KS: Fill Violin histogram with entry from a toy.
TH1D * PolyProjectionY(TObject *poly, const std::string &TempName, const std::vector< double > &ybins, const bool computeErrors)
WP: Poly Projectors.
std::unique_ptr< TH1D > MakeSummaryFromSpectra(const TH2D *Spectra, const std::string &name)
Build a 1D posterior-predictive summary from a violin spectrum.
@ kXSecPar
Definition: MCMCProcessor.h:46
#define MACH3LOG_DEBUG
Definition: MaCh3Logger.h:34
#define MACH3LOG_ERROR
Definition: MaCh3Logger.h:37
#define MACH3LOG_INFO
Definition: MaCh3Logger.h:35
#define MACH3LOG_WARN
Definition: MaCh3Logger.h:36
void AccumulateWAICToy(const double neg_LLH_temp, double &mean_llh, double &mean_llh_squared, double &sum_exp_llh)
bool CheckBounds(const std::vector< const M3::float_t * > &BoundValuePointer, const std::vector< std::pair< double, double >> &ParamBounds)
void AccumulateWAICBin(double &mean_llh, double &mean_llh_squared, double &sum_exp_llh, const unsigned int Ntoys, double &lppd, double &p_WAIC)
int Ntoys
double GetBetaParameter(const double data, const double mc, const double w2, const TestStatistic TestStat)
KS: Calculate Beta parameter which will be different based on specified test statistic.
double GetBIC(const double llh, const int data, const int nPars)
Get the Bayesian Information Criterion (BIC) or Schwarz information criterion (also SIC,...
void Get2DBayesianpValue(TH2D *Histogram)
Calculates the 2D Bayesian p-value and generates a visualization.
YAML::Node TMacroToYAML(const TMacro &macro)
KS: Convert a ROOT TMacro object to a YAML node.
Definition: YamlHelper.h:152
bool compareYAMLNodes(const YAML::Node &node1, const YAML::Node &node2, bool Mute=false)
Compare if yaml nodes are identical.
Definition: YamlHelper.h:186
bool CheckNodeExists(const YAML::Node &node, Args... args)
KS: Wrapper function to call the recursive helper.
Definition: YamlHelper.h:60
Base class for implementing fitting algorithms.
Definition: FitterBase.h:29
std::string GetName() const
Get name of class.
Definition: FitterBase.h:75
std::unique_ptr< TRandom3 > random
Random number.
Definition: FitterBase.h:149
TFile * outputFile
Output.
Definition: FitterBase.h:152
std::string AlgorithmName
Name of fitting algorithm that is being used.
Definition: FitterBase.h:173
std::vector< SampleHandlerInterface * > samples
Sample holder.
Definition: FitterBase.h:134
Manager * fitMan
The manager for configuration handling.
Definition: FitterBase.h:113
void SanitiseInputs()
Remove obsolete memory and make other checks before fit starts.
Definition: FitterBase.cpp:221
std::vector< ParameterHandlerBase * > systematics
Systematic holder.
Definition: FitterBase.h:139
Class responsible for processing MCMC chains, performing diagnostics, generating plots,...
Definition: MCMCProcessor.h:61
void Initialise()
Scan chain, what parameters we have and load information from covariance matrices.
YAML::Node GetCovConfig(const int i) const
Get Yaml config obtained from a Chain.
Custom exception class used throughout MaCh3.
The manager class is responsible for managing configurations and settings.
Definition: Manager.h:16
YAML::Node const & raw() const
Return config.
Definition: Manager.h:47
Base class for handling systematic uncertainty parameters.
int GetNumParams() const
Get total number of parameters.
void SetParProp(const int i, const double val)
Set proposed parameter value.
int GetParIndex(const std::string &name) const
Get index based on name.
std::string GetName() const
Get name of covariance.
double GetParPreFit(const int i) const
Get prior parameter value.
YAML::Node GetConfig() const
Getter to return a copy of the YAML node.
Class responsible for handling of systematic error parameters with different types defined in the con...
std::vector< std::string > GetUniqueParameterGroups() const
KS: Get names of all unique parameter groups.
void SetGroupOnlyParameters(const std::string &Group, const std::vector< double > &Pars={})
KS Function to set to prior parameters of a given group or values from vector.
std::vector< double > PenaltyTerm
Penalty term values for each toy by default 0.
virtual ~PredictiveThrower()
Destructor.
void ExtractLLH(TH1 *DatHist, TH1 *MCHist, TH1 *W2Hist, const SampleHandlerInterface *SampleHandler) const
Calculate the LLH for TH1, set the LLH to title of MCHist.
bool FullLLH
KS: Use Full LLH or only sample contribution based on discussion with Asher we almost always only wan...
void WriteToy(TDirectory *ToyDirectory, TDirectory *Toy_1DDirectory, TDirectory *Toy_2DDirectory, const int iToy)
Save histograms for a single MCMC Throw/Toy.
void StudyCorrelations(TDirectory *PredictiveDir, const std::vector< std::vector< std::unique_ptr< TH1 >>> &Toys, const bool DebugHistograms) const
Study Prior/Posterior correlations between samples etc.
void RunPredictiveAnalysis()
Main routine responsible for producing posterior predictive distributions and $p$-value.
bool LoadToys()
Load existing toys.
void PosteriorPredictivepValue(const std::vector< std::unique_ptr< TH1 >> &PostPred_mc, const std::vector< TDirectory * > &SampleDir)
Calculate Posterior Predictive $p$-value Compares observed data to toy datasets generated from:
void WriteByModeToys(TDirectory *ByModeDirectory, const int iToy)
Save mode histograms for a single MCMC Throw/Toy.
void StudyBIC(const std::vector< std::unique_ptr< TH1 >> &PostPred_mc, const std::vector< std::unique_ptr< TH1 >> &PostPred_w)
Study Bayesian Information Criterion (BIC) The BIC is defined as:
void SetupToyGeneration(std::vector< std::string > &ParameterGroupsNotVaried, std::unordered_set< int > &ParameterOnlyToVary, std::vector< const M3::float_t * > &BoundValuePointer, std::vector< std::pair< double, double >> &ParamBounds)
Setup useful variables etc before stating toy generation.
std::string GetBinName(TH1 *hist, const bool uniform, const int Dim, const std::vector< int > &bins) const
Construct a human-readable label describing a specific analysis bin.
std::vector< std::string > GetStoredFancyName(ParameterHandlerBase *Systematics) const
Get Fancy parameters stored in mcmc chains for passed ParameterHandler.
std::vector< std::unique_ptr< TH1 > > W2_Nom_Hist
Vector of W2 histograms.
std::vector< std::vector< std::unique_ptr< TH1 > > > W2_Hist_Toy
bool Is_PriorPredictive
Whether it is Prior or Posterior predictive.
int NModelParams
KS: Count total number of model parameters which can be used for stuff like BIC.
void MakeCutEventRate(TH1D *Histogram, const double DataRate) const
Make the 1D Event Rate Hist.
void StudyInformationCriterion(M3::kInfCrit Criterion, const std::vector< std::unique_ptr< TH1 >> &PostPred_mc, const std::vector< std::unique_ptr< TH1 >> &PostPred_w)
Information Criterion.
int Ntoys
Number of toys we are generating analysing.
void StudyByMode1DProjections(const std::vector< TDirectory * > &SampleDirectories) const
Load 1D projections by mode and produce post pred for each.
void PredictiveLLH(const std::vector< std::unique_ptr< TH1 >> &Data_histogram, const std::vector< std::unique_ptr< TH1 >> &PostPred_mc, const std::vector< std::unique_ptr< TH1 >> &PostPred_w, const std::vector< TDirectory * > &SampleDir)
Calculate Posterior Predictive LLH.
void RateAnalysis(const std::vector< std::vector< std::unique_ptr< TH1 >>> &Toys, const std::vector< TDirectory * > &SampleDirectories) const
Produce distribution of number of events for each sample.
void MakeChi2Plots(const std::vector< std::vector< double >> &Chi2_x, const std::string &Chi2_x_title, const std::vector< std::vector< double >> &Chi2_y, const std::string &Chi2_y_title, const std::vector< TDirectory * > &SampleDir, const std::string Title)
Produce Chi2 plot for a single sample based on which $p$-value is calculated.
void SetParamters(std::vector< std::string > &ParameterGroupsNotVaried, std::unordered_set< int > &ParameterOnlyToVary)
This set some params to prior value this way you can evaluate errors from subset of errors.
void StudyWAIC()
KS: Get the Watanabe-Akaike information criterion (WAIC)
void Study1DProjections(const std::vector< TDirectory * > &SampleDirectories) const
Load 1D projections and later produce violin plots for each.
void SetupSampleInformation()
Setup sample information.
std::vector< std::unique_ptr< TH1 > > MC_Nom_Hist
Vector of MC histograms.
int TotalNumberOfSamples
Number of toys we are generating analysing.
void ProduceToys()
Produce toys by throwing from MCMC.
void StudyBetaParameters(TDirectory *PredictiveDir)
Evaluate prior/post predictive distribution for beta parameters (used for evaluating impact MC statis...
double CalcLLH(const double data, const double mc, const double w2, const SampleHandlerInterface *SampleHandler) const
Calculates the -2LLH (likelihood) for a single sample.
std::vector< std::unique_ptr< TH1 > > Data_Hist
Vector of Data histograms.
bool StandardFluctuation
KS: We have two methods for Poissonian fluctuation.
ParameterHandlerGeneric * ModelSystematic
Pointer to El Generico.
std::vector< double > ReweightWeight
Reweighting factors applied for each toy, by default 1.
std::vector< std::unique_ptr< TH1 > > MakePredictive(const std::vector< std::vector< std::unique_ptr< TH1 >>> &Toys, const std::vector< TDirectory * > &Director, const std::string &suffix, const bool DebugHistograms, const bool WriteHist)
Produce posterior predictive distribution.
PredictiveThrower(Manager *const fitMan)
Constructor.
void MakeFluctuatedHistogram(TH1 *FluctHist, TH1 *PolyHist)
Make Poisson fluctuation of TH1D hist.
std::vector< std::vector< std::unique_ptr< TH1 > > > MC_Hist_Toy
void StudyDIC(const std::vector< std::unique_ptr< TH1 >> &PostPred_mc, const std::vector< std::unique_ptr< TH1 >> &PostPred_w)
KS: Get the Deviance Information Criterion (DIC) The deviance is defined as:
double GetLLH(const TH1D *DatHist, const TH1D *MCHist, const TH1D *W2Hist, const SampleHandlerInterface *SampleHandler) const
Helper functions to calculate likelihoods using TH1D.
void ProduceSpectra(const std::vector< std::vector< std::vector< std::unique_ptr< TH1D >>>> &Toys, const std::vector< TDirectory * > &Director, const std::string suffix, const bool DoSummary=true) const
Produce Violin style spectra.
std::vector< std::unique_ptr< TH1D > > PerBinHistogram(TH1 *hist, const int SampleId, const int Dim, const std::string &suffix) const
Create per-bin posterior histograms for a given sample.
Class responsible for handling implementation of samples used in analysis, reweighting and returning ...
double GetTestStatLLH(const double data, const double mc, const double w2) const
Calculate test statistic for a single bin. Calculation depends on setting of fTestStatistic....
int getValue(const std::string &Type)
CW: Get info like RAM.
Definition: Monitor.cpp:252
void PrintProgressBar(const Long64_t Done, const Long64_t All)
KS: Simply print progress bar.
Definition: Monitor.cpp:229
kInfCrit
KS: Different Information Criterion tests mostly based Gelman paper.
@ kWAIC
Watanabe-Akaike information criterion.
@ kInfCrits
This only enumerates.
@ kBIC
Bayesian Information Criterion.
@ kDIC
Deviance Information Criterion.
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.
TFile * Open(const std::string &Name, const std::string &Type, const std::string &File, const int Line)
Opens a ROOT file with the given name and mode.
constexpr static const int _BAD_INT_
Default value used for int initialisation.
Definition: Core.h:55
KS: Store info about MC sample.
Definition: SampleInfo.h:40