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