MaCh3  2.8.0
Reference Guide
FitterBase.cpp
Go to the documentation of this file.
2 #include <vector>
3 #include <memory>
4 
6 #include "TStopwatch.h"
7 #include "TTree.h"
8 #include "TGraphAsymmErrors.h"
9 #include "TMath.h"
11 
12 #pragma GCC diagnostic ignored "-Wuseless-cast"
13 
14 // *************************
15 // Initialise the Manager and make it an object of FitterBase class
16 // Now we can dump Manager settings to the output file
17 FitterBase::FitterBase(Manager * const man) : fitMan(man) {
18 // *************************
19  AlgorithmName = "";
20  //Get mach3 modes from Manager
21  auto Seed = Get<int>(fitMan->raw()["General"]["Seed"], __FILE__, __LINE__);
22  M3::rand::SetSeed(Seed);
23  // Counter of the accepted # of steps
24  accCount = 0;
25  step = 0;
26  stepStart = 0;
27 
28  clock = std::make_unique<TStopwatch>();
29  stepClock = std::make_unique<TStopwatch>();
30  #ifdef MACH3_DEBUG
31  // Fit summary and debug info
32  debug = GetFromManager<bool>(fitMan->raw()["General"]["Debug"], false, __FILE__ , __LINE__);
33  #endif
34 
35  auto outfile = Get<std::string>(fitMan->raw()["General"]["OutputFile"], __FILE__ , __LINE__);
36  // Save output every auto_save steps
37  //you don't want this too often https://root.cern/root/html606/TTree_8cxx_source.html#l01229
38  auto_save = Get<int>(fitMan->raw()["General"]["MCMC"]["AutoSave"], __FILE__ , __LINE__);
39 
40  // Set the output file
41  outputFile = M3::Open(outfile, "RECREATE", __FILE__, __LINE__);
42  outputFile->cd();
43  // Set output tree
44  outTree = new TTree("posteriors", "Posterior_Distributions");
45  // Auto-save every 200MB, the bigger the better https://root.cern/root/html606/TTree_8cxx_source.html#l01229
46  outTree->SetAutoSave(-200E6);
47 
48  FileSaved = false;
49  SettingsSaved = false;
50  OutputPrepared = false;
51 
52  //Create TDirectory
53  CovFolder = outputFile->mkdir("CovarianceFolder");
54  outputFile->cd();
55  SampleFolder = outputFile->mkdir("SampleFolder");
56  outputFile->cd();
57 
58  #ifdef MACH3_DEBUG
59  // Prepare the output log file
60  if (debug) debugFile.open((outfile+".log").c_str());
61  #endif
62 
63  TotalNSamples = 0;
64  fTestLikelihood = GetFromManager<bool>(fitMan->raw()["General"]["Fitter"]["FitTestLikelihood"], false, __FILE__ , __LINE__);
65 }
66 
67 // *************************
68 // Destructor: close the logger and output file
70 // *************************
71  SaveOutput();
72  if(outputFile != nullptr) delete outputFile;
73  outputFile = nullptr;
74  MACH3LOG_DEBUG("Closing MaCh3 Fitter Engine");
75 }
76 
77 // *******************
78 // Prepare the output tree
80 // *******************
81  if(SettingsSaved) return;
82 
83  outputFile->cd();
84 
85  TDirectory* MaCh3Version = outputFile->mkdir("MaCh3Engine");
86  MaCh3Version->cd();
87 
88  if (std::getenv("MaCh3_ROOT") == nullptr) {
89  MACH3LOG_ERROR("Need MaCh3_ROOT environment variable");
90  MACH3LOG_ERROR("Please remember about source bin/setup.MaCh3.sh");
91  throw MaCh3Exception(__FILE__ , __LINE__ );
92  }
93 
94  if (std::getenv("MACH3") == nullptr) {
95  MACH3LOG_ERROR("Need MACH3 environment variable");
96  throw MaCh3Exception(__FILE__ , __LINE__ );
97  }
98 
99  std::string header_path = std::string(std::getenv("MACH3"));
100  header_path += "/version.h";
101  FILE* file = fopen(header_path.c_str(), "r");
102  //KS: It is better to use experiment specific header file. If given experiment didn't provide it we gonna use one given by Core MaCh3.
103  if (!file) {
104  header_path = std::string(std::getenv("MaCh3_ROOT"));
105  header_path += "/version.h";
106  } else {
107  fclose(file);
108  }
109 
110  // EM: embed the cmake generated version.h file
111  TMacro versionHeader("version_header", "version_header");
112  versionHeader.ReadFile(header_path.c_str());
113  versionHeader.Write();
114 
115  auto Name = GetFancyName();
116  if(Name == ""){
117  MACH3LOG_ERROR("Name of currently used algorithm is {}", Name);
118  MACH3LOG_ERROR("Have you forgotten to modify AlgorithmName?");
119  throw MaCh3Exception(__FILE__ , __LINE__ );
120  }
121  TNamed Engine(Name, Name);
122  Engine.Write(Name.c_str());
123 
124  MaCh3Version->Write();
125  delete MaCh3Version;
126 
127  outputFile->cd();
128 
130 
131  MACH3LOG_WARN("\033[0;31mCurrent Total RAM usage is {:.2f} GB\033[0m", M3::Utils::getValue("VmRSS") / 1048576.0);
132  MACH3LOG_WARN("\033[0;31mOut of Total available RAM {:.2f} GB\033[0m", M3::Utils::getValue("MemTotal") / 1048576.0);
133 
134  MACH3LOG_INFO("#####Current Setup#####");
135  MACH3LOG_INFO("Number of covariances: {}", systematics.size());
136  for(unsigned int i = 0; i < systematics.size(); ++i){
137  MACH3LOG_INFO("{}: Cov name: {}, it has {} params", i, systematics[i]->GetName(), systematics[i]->GetNumParams());
138  }
139  MACH3LOG_INFO("Number of SampleHandlers: {}", samples.size());
140  for(unsigned int i = 0; i < samples.size(); ++i) {
141  MACH3LOG_INFO("{}: SampleHandler name: {}, it has {} samples",i , samples[i]->GetName(), samples[i]->GetNSamples());
142  for(int iSam = 0; iSam < samples[i]->GetNSamples(); ++iSam) {
143  MACH3LOG_INFO(" {}: Sample title: {}, with {} osc channels",iSam , samples[i]->GetSampleTitle(iSam), samples[i]->GetNOscChannels(iSam));
144  }
145  }
146  //TN: Have to close the folder in order to write it to disk before SaveOutput is called in the destructor
147  CovFolder->Close();
148  SampleFolder->Close();
149 
150  SettingsSaved = true;
151 }
152 
153 // *******************
154 // Prepare the output tree
156 // *******************
157  if(OutputPrepared) return;
158  //MS: Check if we are fitting the test likelihood, rather than T2K likelihood, and only setup T2K output if not
159  if(!fTestLikelihood)
160  {
161  // Check that we have added samples
162  if (!samples.size()) {
163  MACH3LOG_CRITICAL("No samples Found! Is this really what you wanted?");
164  //throw MaCh3Exception(__FILE__ , __LINE__ );
165  }
166 
167  // Do we want to save proposal? This will break plotting scripts and is heave for disk space and step time. Only use when debugging
168  bool SaveProposal = GetFromManager<bool>(fitMan->raw()["General"]["SaveProposal"], false, __FILE__ , __LINE__);
169 
170  if(SaveProposal) MACH3LOG_INFO("Will save in the chain proposal parameters and LogL");
171  // Prepare the output trees
172  for (ParameterHandlerBase *cov : systematics) {
173  cov->SetBranches(*outTree, SaveProposal);
174  }
175 
176  outTree->Branch("LogL", &logLCurr, "LogL/D");
177  if(SaveProposal) outTree->Branch("LogLProp", &logLProp, "LogLProp/D");
178  outTree->Branch("accProb", &accProb, "accProb/D");
179  outTree->Branch("step", &step, "step/i");
180  outTree->Branch("stepTime", &stepTime, "stepTime/D");
181 
182  // Store individual likelihood components
183  // Using double means double as large output file!
184  sample_llh.resize(samples.size());
185  syst_llh.resize(systematics.size());
186 
187  for (size_t i = 0; i < samples.size(); ++i) {
188  std::stringstream oss, oss2;
189  oss << "LogL_sample_" << i;
190  oss2 << oss.str() << "/D";
191  outTree->Branch(oss.str().c_str(), &sample_llh[i], oss2.str().c_str());
192  }
193 
194  for (size_t i = 0; i < systematics.size(); ++i) {
195  std::stringstream oss, oss2;
196  oss << "LogL_systematic_" << systematics[i]->GetName();
197  oss2 << oss.str() << "/D";
198  outTree->Branch(oss.str().c_str(), &syst_llh[i], oss2.str().c_str());
199  }
200  }
201  else
202  {
203  outTree->Branch("LogL", &logLCurr, "LogL/D");
204  outTree->Branch("accProb", &accProb, "accProb/D");
205  outTree->Branch("step", &step, "step/i");
206  outTree->Branch("stepTime", &stepTime, "stepTime/D");
207  }
208 
209  MACH3LOG_INFO("-------------------- Starting MCMC --------------------");
210  #ifdef MACH3_DEBUG
211  if (debug) {
212  debugFile << "----- Starting MCMC -----" << std::endl;
213  }
214  #endif
215  // Time the progress
216 
217 
218  clock->Start();
219 
220 
221  OutputPrepared = true;
222 }
223 
224 // *******************
226 // *******************
227  for (size_t i = 0; i < samples.size(); ++i) {
228  samples[i]->CleanMemoryBeforeFit();
229  }
230 }
231 
232 // *******************
234 // *******************
235  if(FileSaved) return;
236  //Stop Clock
237  clock->Stop();
238 
239  //KS: Some version of ROOT keep spamming about accessing already deleted object which is wrong and not helpful...
240  int originalErrorLevel = gErrorIgnoreLevel;
241  gErrorIgnoreLevel = kFatal;
242 
243  outputFile->cd();
244  outTree->Write();
245 
246  MACH3LOG_INFO("{} steps took {:.2e} seconds to complete. ({:.2e}s / step).", step - stepStart, clock->RealTime(), clock->RealTime() / static_cast<double>(step - stepStart));
247  MACH3LOG_INFO("{} steps were accepted.", accCount);
248  #ifdef MACH3_DEBUG
249  if (debug)
250  {
251  debugFile << "\n\n" << step << " steps took " << clock->RealTime() << " seconds to complete. (" << clock->RealTime() / step << "s / step).\n" << accCount<< " steps were accepted." << std::endl;
252  debugFile.close();
253  }
254  #endif
255 
256  outputFile->Close();
257  FileSaved = true;
258 
259  gErrorIgnoreLevel = originalErrorLevel;
260 }
261 
262 // *************************
263 // Add SampleHandler object to the Markov Chain
265 // *************************
266  // Check if any subsample name collides with already-registered subsamples
267  for (const auto &s : samples) {
268  for (int iExisting = 0; iExisting < s->GetNSamples(); ++iExisting) {
269  for (int iNew = 0; iNew < sample->GetNSamples(); ++iNew) {
270  if (s->GetSampleTitle(iExisting) == sample->GetSampleTitle(iNew)) {
272  "Duplicate sample title '{}' in handler {} detected: "
273  "same title exist in handler ", sample->GetSampleTitle(iNew),
274  sample->GetName(), s->GetName());
275  throw MaCh3Exception(__FILE__, __LINE__);
276  }
277  }
278  }
279  }
280 
281  for (const auto &s : samples) {
282  if (s->GetName() == sample->GetName()) {
283  MACH3LOG_ERROR("SampleHandler with name '{}' already exists!", sample->GetName());
284  MACH3LOG_ERROR("Is it intended?");
285  throw MaCh3Exception(__FILE__ , __LINE__ );
286  }
287  }
288  // Save additional info from samples
289  SampleFolder->cd();
290 
292  TotalNSamples += sample->GetNSamples();
293  MACH3LOG_INFO("Adding {} object, with {} samples", sample->GetName(), sample->GetNSamples());
294  samples.push_back(sample);
295  outputFile->cd();
296 }
297 
298 // *************************
299 // Add flux systematics, cross-section systematics, ND systematics to the chain
301 // *************************
302  MACH3LOG_INFO("Adding systematic object {}, with {} params", cov->GetName(), cov->GetNumParams());
303  // KS: Need to make sure we don't have params with same name, otherwise ROOT I/O and parts of MaCh3 will be terribly confused...
304  for (size_t s = 0; s < systematics.size(); ++s)
305  {
306  for (int iPar = 0; iPar < systematics[s]->GetNumParams(); ++iPar)
307  {
308  for (int i = 0; i < cov->GetNumParams(); ++i)
309  {
310  if(systematics[s]->GetParName(iPar) == cov->GetParName(i)){
311  MACH3LOG_ERROR("ParameterHandler {} has param '{}' which already exists in in {}, with name {}",
312  cov->GetName(), cov->GetParName(i), systematics[s]->GetName(), systematics[s]->GetParName(iPar));
313  throw MaCh3Exception(__FILE__ , __LINE__ );
314  }
315  // Same for fancy name
316  if(systematics[s]->GetParFancyName(iPar) == cov->GetParFancyName(i)){
317  MACH3LOG_ERROR("ParameterHandler {} has param '{}' which already exists in {}, with name {}",
318  cov->GetName(), cov->GetParFancyName(i), systematics[s]->GetName(), systematics[s]->GetParFancyName(iPar));
319  throw MaCh3Exception(__FILE__ , __LINE__ );
320  }
321  }
322  }
323  }
324 
325  systematics.push_back(cov);
326 
327  CovFolder->cd();
328  std::vector<double> n_vec(cov->GetNumParams());
329  for (int i = 0; i < cov->GetNumParams(); ++i) {
330  n_vec[i] = cov->GetParPreFit(i);
331  }
332  cov->GetCovMatrix()->Write(cov->GetName().c_str());
333 
334  TH2D* CorrMatrix = cov->GetCorrelationMatrix();
335  CorrMatrix->Write((cov->GetName() + std::string("_Corr")).c_str());
336  delete CorrMatrix;
337 
338  // If we have yaml config file for covariance let's save it
339  YAML::Node Config = cov->GetConfig();
340  if(!Config.IsNull())
341  {
342  TMacro ConfigSave = YAMLtoTMacro(Config, (std::string("Config_") + cov->GetName()));
343  ConfigSave.Write();
344  }
345 
346  outputFile->cd();
347 }
348 
349 // *******************
350 void FitterBase::StartFromPreviousFit(const std::string& FitName) {
351 // *******************
352  MACH3LOG_INFO("Getting starting position from {}", FitName);
353  TFile *infile = M3::Open(FitName, "READ", __FILE__, __LINE__);
354  TTree *posts = infile->Get<TTree>("posteriors");
355  double log_val = M3::_LARGE_LOGL_;
356  posts->SetBranchAddress("LogL",&log_val);
357 
358  for (size_t s = 0; s < systematics.size(); ++s)
359  {
360  TDirectory* CovarianceFolder = infile->Get<TDirectory>("CovarianceFolder");
361 
362  std::string ConfigName = "Config_" + systematics[s]->GetName();
363  TMacro *ConfigCov = CovarianceFolder->Get<TMacro>(ConfigName.c_str());
364  // KS: Not every covariance uses yaml, if it uses yaml make sure they are identical
365  if (ConfigCov != nullptr) {
366  // Config which was in MCMC from which we are starting
367  YAML::Node CovSettings = TMacroToYAML(*ConfigCov);
368  // Config from currently used cov object
369  YAML::Node ConfigCurrent = systematics[s]->GetConfig();
370 
371  if (!compareYAMLNodes(CovSettings, ConfigCurrent))
372  {
373  MACH3LOG_ERROR("Yaml configs in previous chain (from path {}) and current one are different", FitName);
374  throw MaCh3Exception(__FILE__ , __LINE__ );
375  }
376  delete ConfigCov;
377  }
378 
379  CovarianceFolder->Close();
380  delete CovarianceFolder;
381 
382  std::vector<double> branch_vals;
383  std::vector<std::string> branch_name;
384  systematics[s]->MatchMaCh3OutputBranches(posts, branch_vals, branch_name);
385  posts->GetEntry(posts->GetEntries()-1);
386 
387  systematics[s]->SetParameters(branch_vals);
388  systematics[s]->AcceptStep();
389 
390  MACH3LOG_INFO("Printing new starting values for: {}", systematics[s]->GetName());
391  systematics[s]->PrintPreFitCurrPropValues();
392 
393  // Resetting branch addressed to nullptr as we don't want to write into a deleted vector out of scope...
394  for (int i = 0; i < systematics[s]->GetNumParams(); ++i) {
395  posts->SetBranchAddress(systematics[s]->GetParName(i).c_str(), nullptr);
396  }
397  }
398  logLCurr = log_val;
399  logLProp = log_val;
400 
401  delete posts;
402  infile->Close();
403  delete infile;
404 }
405 
406 // *******************
407 // Process the MCMC output to get postfit etc
409 // *******************
410  if (fitMan == nullptr) return;
411 
412  // Process the MCMC
413  if (GetFromManager<bool>(fitMan->raw()["General"]["ProcessMCMC"], false, __FILE__ , __LINE__)){
414  // Make the processor
415  MCMCProcessor Processor(std::string(outputFile->GetName()));
416 
417  Processor.Initialise();
418  // Make the TVectorD pointers which hold the processed output
419  TVectorD *Central = nullptr;
420  TVectorD *Errors = nullptr;
421  TVectorD *Central_Gauss = nullptr;
422  TVectorD *Errors_Gauss = nullptr;
423  TVectorD *Peaks = nullptr;
424 
425  // Make the postfit
426  Processor.GetPostfit(Central, Errors, Central_Gauss, Errors_Gauss, Peaks);
427  Processor.DrawPostfit();
428 
429  // Make the TMatrix pointers which hold the processed output
430  TMatrixDSym *Covariance = nullptr;
431  TMatrixDSym *Correlation = nullptr;
432 
433  // Make the covariance matrix
434  Processor.GetCovariance(Covariance, Correlation);
435  Processor.DrawCovariance();
436 
437  std::vector<TString> BranchNames = Processor.GetBranchNames();
438 
439  // Re-open the TFile
440  if (!outputFile->IsOpen()) {
441  MACH3LOG_INFO("Opening output again to update with means..");
442  outputFile = new TFile(Get<std::string>(fitMan->raw()["General"]["OutputFile"], __FILE__, __LINE__).c_str(), "UPDATE");
443  }
444  Central->Write("PDF_Means");
445  Errors->Write("PDF_Errors");
446  Central_Gauss->Write("Gauss_Means");
447  Errors_Gauss->Write("Errors_Gauss");
448  Covariance->Write("Covariance");
449  Correlation->Write("Correlation");
450  }
451 }
452 
453 // *************************
454 // Run Drag Race
455 void FitterBase::DragRace(const int NLaps) {
456 // *************************
457  MACH3LOG_INFO("Let the Race Begin!");
458  MACH3LOG_INFO("All tests will be performed with {} threads", M3::GetNThreads());
459 
460  // Reweight the MC
461  for(unsigned int ivs = 0; ivs < samples.size(); ++ivs)
462  {
463  TStopwatch clockRace;
464  clockRace.Start();
465  for(int Lap = 0; Lap < NLaps; ++Lap) {
466  samples[ivs]->Reweight();
467  }
468  clockRace.Stop();
469  MACH3LOG_INFO("It took {:.4f} s to reweights {} times sample: {}", clockRace.RealTime(), NLaps, samples[ivs]->GetName());
470  MACH3LOG_INFO("On average {:.6f}", clockRace.RealTime()/NLaps);
471  }
472 
473  for(unsigned int ivs = 0; ivs < samples.size(); ++ivs)
474  {
475  TStopwatch clockRace;
476  clockRace.Start();
477  for(int Lap = 0; Lap < NLaps; ++Lap) {
478  samples[ivs]->GetLikelihood();
479  }
480  clockRace.Stop();
481  MACH3LOG_INFO("It took {:.4f} s to calculate GetLikelihood {} times sample: {}", clockRace.RealTime(), NLaps, samples[ivs]->GetName());
482  MACH3LOG_INFO("On average {:.6f}", clockRace.RealTime()/NLaps);
483  }
484  // Get vector of proposed steps. If we want to run LLH scan or something else after we need to revert changes after proposing steps multiple times
485  std::vector<std::vector<double>> StepsValuesBefore(systematics.size());
486  for (size_t s = 0; s < systematics.size(); ++s) {
487  StepsValuesBefore[s] = systematics[s]->GetProposed();
488  }
489  for (size_t s = 0; s < systematics.size(); ++s) {
490  TStopwatch clockRace;
491  clockRace.Start();
492  for(int Lap = 0; Lap < NLaps; ++Lap) {
493  systematics[s]->ProposeStep();
494  }
495  clockRace.Stop();
496  MACH3LOG_INFO("It took {:.4f} s to propose step {} times cov: {}", clockRace.RealTime(), NLaps, systematics[s]->GetName());
497  MACH3LOG_INFO("On average {:.6f}", clockRace.RealTime()/NLaps);
498  }
499  for (size_t s = 0; s < systematics.size(); ++s) {
500  systematics[s]->SetParameters(StepsValuesBefore[s]);
501  }
502 
503  for (size_t s = 0; s < systematics.size(); ++s) {
504  TStopwatch clockRace;
505  clockRace.Start();
506  for(int Lap = 0; Lap < NLaps; ++Lap) {
507  systematics[s]->GetLikelihood();
508  }
509  clockRace.Stop();
510  MACH3LOG_INFO("It took {:.4f} s to calculate get likelihood {} times cov: {}", clockRace.RealTime(), NLaps, systematics[s]->GetName());
511  MACH3LOG_INFO("On average {:.6f}", clockRace.RealTime()/NLaps);
512  }
513  MACH3LOG_INFO("End of race");
514 }
515 
516 // *************************
517 bool FitterBase::GetScanRange(std::map<std::string, std::vector<double>>& scanRanges) const {
518 // *************************
519  bool isScanRanges = false;
520  // YSP: Set up a mapping to store parameters with user-specified ranges, suggested by D. Barrow
521  if(fitMan->raw()["LLHScan"]["ScanRanges"]){
522  YAML::Node scanRangesList = fitMan->raw()["LLHScan"]["ScanRanges"];
523  for (auto it = scanRangesList.begin(); it != scanRangesList.end(); ++it) {
524  std::string itname = it->first.as<std::string>();
525  std::vector<double> itrange = it->second.as<std::vector<double>>();
526  // Set the mapping as param_name:param_range
527  scanRanges[itname] = itrange;
528  }
529  isScanRanges = true;
530  } else {
531  MACH3LOG_INFO("There are no user-defined parameter ranges, so I'll use default param bounds for LLH Scans");
532  }
533  return isScanRanges;
534 }
535 
536 // *************************
537 bool FitterBase::CheckSkipParameter(const std::vector<std::string>& SkipVector, const std::string& ParamName) const {
538 // *************************
539  return M3::CaseInsensitiveMatchAny(ParamName, SkipVector);
540 }
541 
542 // *************************
543 void FitterBase::GetParameterScanRange(const ParameterHandlerBase* cov, const int i, double& CentralValue,
544  double& lower, double& upper, const int n_points, const std::string& suffix) const {
545 // *************************
546  // YSP: Set up a mapping to store parameters with user-specified ranges, suggested by D. Barrow
547  std::map<std::string, std::vector<double>> scanRanges;
548  const bool isScanRanges = GetScanRange(scanRanges);
549 
550  double nSigma = GetFromManager<int>(fitMan->raw()["LLHScan"]["LLHScanSigma"], 1., __FILE__, __LINE__);
551  bool IsPCA = cov->IsPCA();
552 
553  // Get the parameter name
554  std::string name = cov->GetParFancyName(i);
555  if (IsPCA) name += "_PCA";
556 
557  // Get the parameter priors and bounds
558  CentralValue = cov->GetParProp(i);
559  if (IsPCA) CentralValue = cov->GetPCAHandler()->GetParPropPCA(i);
560 
561  double prior = cov->GetParPreFit(i);
562  if (IsPCA) prior = cov->GetPCAHandler()->GetPreFitValuePCA(i);
563 
564  if (std::abs(CentralValue - prior) > 1e-10) {
565  MACH3LOG_INFO("For {} scanning around value {:.4f} rather than prior {:.4f}", name, CentralValue, prior);
566  }
567 
568  // Get the covariance matrix and do the +/- nSigma
569  // Set lower and upper bounds relative the CentralValue
570  // Set the parameter ranges between which LLH points are scanned
571  lower = CentralValue - nSigma*cov->GetDiagonalError(i);
572  upper = CentralValue + nSigma*cov->GetDiagonalError(i);
573  // If PCA, transform these parameter values to the PCA basis
574  if (IsPCA) {
575  lower = CentralValue - nSigma*std::sqrt((cov->GetPCAHandler()->GetEigenValues())(i));
576  upper = CentralValue + nSigma*std::sqrt((cov->GetPCAHandler()->GetEigenValues())(i));
577  MACH3LOG_INFO("eval {} = {:.2f}", i, cov->GetPCAHandler()->GetEigenValues()(i));
578  MACH3LOG_INFO("CV {} = {:.2f}", i, CentralValue);
579  MACH3LOG_INFO("lower {} = {:.2f}", i, lower);
580  MACH3LOG_INFO("upper {} = {:.2f}", i, upper);
581  MACH3LOG_INFO("nSigma = {:.2f}", nSigma);
582  }
583 
584  // Implementation suggested by D. Barrow
585  // If param ranges are specified in scanRanges node, extract it from there
586  if(isScanRanges){
587  // Find matching entries through std::maps
588  auto it = scanRanges.find(name);
589  if (it != scanRanges.end() && it->second.size() == 2) { //Making sure the range is has only two entries
590  lower = it->second[0];
591  upper = it->second[1];
592  MACH3LOG_INFO("Found matching param name for setting specified range for {}", name);
593  MACH3LOG_INFO("Range for {} = [{:.2f}, {:.2f}]", name, lower, upper);
594  }
595  }
596 
597  // Cross-section and flux parameters have boundaries that we scan between, check that these are respected in setting lower and upper variables
598  // This also applies for other parameters like osc, etc.
599  lower = std::max(lower, cov->GetLowerBound(i));
600  upper = std::min(upper, cov->GetUpperBound(i));
601  MACH3LOG_INFO("Scanning {} {} with {} steps, from [{:.2f} , {:.2f}], CV = {:.2f}", suffix, name, n_points, lower, upper, CentralValue);
602 }
603 
604 // *************************
605 // Calculate bin edges for logarithmic LLH scans
606 std::vector<double>FitterBase::CalculateBinEdges(double lowerlimit, double upperlimit, int n_points) const {
607 // *************************
608  std::vector<double> binEdges(n_points + 1);
609 
610  double logLower = std::log10(lowerlimit);
611  double logUpper = std::log10(upperlimit);
612 
613  for (int j = 0; j <= n_points; ++j) {
614  binEdges[j] = std::pow(10.0, logLower + (logUpper - logLower) * double(j) / double(n_points));
615  }
616 
617  return binEdges;
618 }
619 
620 // *************************
621 // Run LLH scan
623 // *************************
624  // Save the settings into the output file
625  SaveSettings();
626 
627  MACH3LOG_INFO("Starting {}", __func__);
628 
629  //KS: Turn it on if you want LLH scan for each ND sample separately, which increase time significantly but can be useful for validating new samples or dials.
630  bool PlotLLHScanBySample = GetFromManager<bool>(fitMan->raw()["LLHScan"]["LLHScanBySample"], false, __FILE__ , __LINE__);
631  auto SkipVector = GetFromManager<std::vector<std::string>>(fitMan->raw()["LLHScan"]["LLHScanSkipVector"], {}, __FILE__ , __LINE__);
632  //Do we want a logarithmic LLH scan
633  bool LLHLogarithmic = GetFromManager<bool>(fitMan->raw()["LLHScan"]["LLHLogarithmic"], false, __FILE__ , __LINE__);
634 
635  // Now finally get onto the LLH scan stuff
636  // Very similar code to MCMC but never start MCMC; just scan over the parameter space
637  std::vector<TDirectory *> Cov_LLH(systematics.size());
638  for(unsigned int ivc = 0; ivc < systematics.size(); ++ivc )
639  {
640  std::string NameTemp = systematics[ivc]->GetName();
641  NameTemp = NameTemp.substr(0, NameTemp.find("_cov")) + "_LLH";
642  Cov_LLH[ivc] = outputFile->mkdir(NameTemp.c_str());
643  }
644 
645  std::vector<TDirectory *> SampleClass_LLH(samples.size());
646  for(unsigned int ivs = 0; ivs < samples.size(); ++ivs )
647  {
648  std::string NameTemp = samples[ivs]->GetName();
649  SampleClass_LLH[ivs] = outputFile->mkdir(NameTemp.c_str());
650  }
651 
652  TDirectory *Sample_LLH = outputFile->mkdir("Sample_LLH");
653  TDirectory *Total_LLH = outputFile->mkdir("Total_LLH");
654 
655  std::vector<TDirectory *>SampleSplit_LLH;
656  if(PlotLLHScanBySample)
657  {
658  SampleSplit_LLH.resize(TotalNSamples);
659  int SampleIterator = 0;
660  for(unsigned int ivs = 0; ivs < samples.size(); ++ivs )
661  {
662  for(int is = 0; is < samples[ivs]->GetNSamples(); ++is )
663  {
664  SampleSplit_LLH[SampleIterator] = outputFile->mkdir((samples[ivs]->GetSampleTitle(is)+ "_LLH").c_str());
665  SampleIterator++;
666  }
667  }
668  }
669  // Number of points we do for each LLH scan
670  const int n_points = GetFromManager<int>(fitMan->raw()["LLHScan"]["LLHScanPoints"], 100, __FILE__ , __LINE__);
671 
672  // We print 5 reweights
673  const int countwidth = int(double(n_points)/double(5));
674 
675  // Loop over the covariance classes
676  for (ParameterHandlerBase *cov : systematics)
677  {
678  // Scan over all the parameters
679  // Get the number of parameters
680  int npars = cov->GetNumParams();
681  bool IsPCA = cov->IsPCA();
682  if (IsPCA) npars = cov->GetNParameters();
683  for (int i = 0; i < npars; ++i)
684  {
685  // Get the parameter name
686  std::string name = cov->GetParFancyName(i);
687  if (IsPCA) name += "_PCA";
688  // KS: Check if we want to skip this parameter
689  if(CheckSkipParameter(SkipVector, name)) continue;
690  // Get the parameter central and bounds
691  double CentralValue, lower, upper;
692  GetParameterScanRange(cov, i, CentralValue, lower, upper, n_points);
693  // Define the TH1D
694  std::unique_ptr<TH1D> hScan;
695  // See if we want to do it logarithmically
696  if(LLHLogarithmic){
697  //negative values break it
698  if (lower < 0.0 || upper < 0.0) {
699  MACH3LOG_WARN("Cannot perform logarithmic scan for {} "" with range [{}, {}], falling back to linear scan",name, lower, upper);
700  hScan = std::make_unique<TH1D>((name + "_full").c_str(), (name + "_full").c_str(), n_points, lower, upper);
701  } else {
702  auto binEdges = CalculateBinEdges(lower, upper, n_points);
703  hScan = std::make_unique<TH1D>((name + "_full").c_str(), (name + "_full").c_str(), n_points, binEdges.data());
704  }
705  }
706  //If not then just do the normal thing
707  else {
708  hScan = std::make_unique<TH1D>((name + "_full").c_str(), (name + "_full").c_str(), n_points, lower, upper);
709  }
710 
711  hScan->SetTitle((std::string("2LLH_full, ") + name + ";" + name + "; -2(ln L_{sample} + ln L_{xsec+flux} + ln L_{det})").c_str());
712  hScan->SetDirectory(nullptr);
713 
714  auto hScanSam = std::make_unique<TH1D>((name + "_sam").c_str(), (name + "_sam").c_str(), n_points, lower, upper);
715  hScanSam->SetTitle((std::string("2LLH_sam, ") + name + ";" + name + "; -2(ln L_{sample})").c_str());
716  hScanSam->SetDirectory(nullptr);
717 
718  std::vector<std::unique_ptr<TH1D>> hScanSample(samples.size());
719  std::vector<double> nSamLLH(samples.size(), 0.0);
720  for(unsigned int ivs = 0; ivs < samples.size(); ++ivs )
721  {
722  std::string NameTemp = samples[ivs]->GetName();
723  hScanSample[ivs] = std::make_unique<TH1D>((name+"_"+NameTemp).c_str(), (name+"_" + NameTemp).c_str(), n_points, lower, upper);
724  hScanSample[ivs]->SetDirectory(nullptr);
725  hScanSample[ivs]->SetTitle(("2LLH_" + NameTemp + ", " + name + ";" + name + "; -2(ln L_{" + NameTemp +"})").c_str());
726  }
727 
728  std::vector<std::unique_ptr<TH1D>> hScanCov(systematics.size());
729  std::vector<double> nCovLLH(systematics.size(), 0.0);
730  for(unsigned int ivc = 0; ivc < systematics.size(); ++ivc )
731  {
732  std::string NameTemp = systematics[ivc]->GetName();
733  NameTemp = NameTemp.substr(0, NameTemp.find("_cov"));
734  hScanCov[ivc] = std::make_unique<TH1D>((name + "_" + NameTemp).c_str(), (name + "_" + NameTemp).c_str(), n_points, lower, upper);
735  hScanCov[ivc]->SetDirectory(nullptr);
736  hScanCov[ivc]->SetTitle(("2LLH_" + NameTemp + ", " + name + ";" + name + "; -2(ln L_{" + NameTemp +"})").c_str());
737  }
738 
739  std::vector<std::unique_ptr<TH1D>> hScanSamSplit;
740  std::vector<double> sampleSplitllh;
741  if(PlotLLHScanBySample)
742  {
743  int SampleIterator = 0;
744  hScanSamSplit.resize(TotalNSamples);
745  sampleSplitllh.resize(TotalNSamples);
746  for(unsigned int ivs = 0; ivs < samples.size(); ++ivs )
747  {
748  for(int is = 0; is < samples[ivs]->GetNSamples(); ++is )
749  {
750  auto histName = name + samples[ivs]->GetSampleTitle(is);
751  auto histTitle = std::string("2LLH_sam, ") + name + ";" + name + "; -2(ln L_{sample})";
752  hScanSamSplit[SampleIterator] = std::make_unique<TH1D>(histName.c_str(), histTitle.c_str(), n_points, lower, upper);
753  hScanSamSplit[SampleIterator]->SetDirectory(nullptr);
754  SampleIterator++;
755  }
756  }
757  }
758 
759  // Scan over the parameter space
760 
761  for (int j = 0; j < n_points; ++j)
762  {
763  if (j % countwidth == 0)
764  M3::Utils::PrintProgressBar(j, n_points);
765 
766  // For PCA we have to do it differently
767  if (IsPCA) {
768  cov->GetPCAHandler()->SetParPropPCA(i, hScan->GetBinCenter(j+1));
769  } else {
770  // Set the parameter
771  cov->SetParProp(i, hScan->GetBinCenter(j+1));
772  }
773 
774  // Reweight the MC
775  for(unsigned int ivs = 0; ivs < samples.size(); ++ivs ){
776  samples[ivs]->Reweight();
777  }
778  //Total LLH
779  double totalllh = 0.;
780 
781  // Get the -log L likelihoods
782  double samplellh = 0.;
783 
784  for(unsigned int ivs = 0; ivs < samples.size(); ++ivs ) {
785  nSamLLH[ivs] = samples[ivs]->GetLikelihood();
786  samplellh += nSamLLH[ivs];
787  }
788 
789  for(unsigned int ivc = 0; ivc < systematics.size(); ++ivc ) {
790  nCovLLH[ivc] = systematics[ivc]->GetLikelihood();
791  totalllh += nCovLLH[ivc];
792  }
793 
794  totalllh += samplellh;
795 
796  if(PlotLLHScanBySample)
797  {
798  int SampleIterator = 0;
799  for(unsigned int ivs = 0; ivs < samples.size(); ++ivs )
800  {
801  for(int is = 0; is < samples[ivs]->GetNSamples(); ++is)
802  {
803  sampleSplitllh[SampleIterator] = samples[ivs]->GetSampleLikelihood(is);
804  SampleIterator++;
805  }
806  }
807  }
808 
809  for(unsigned int ivs = 0; ivs < samples.size(); ++ivs ) {
810  hScanSample[ivs]->SetBinContent(j+1, 2*nSamLLH[ivs]);
811  }
812  for(unsigned int ivc = 0; ivc < systematics.size(); ++ivc ) {
813  hScanCov[ivc]->SetBinContent(j+1, 2*nCovLLH[ivc]);
814  }
815 
816  hScanSam->SetBinContent(j+1, 2*samplellh);
817  hScan->SetBinContent(j+1, 2*totalllh);
818 
819  if(PlotLLHScanBySample)
820  {
821  int SampleIterator = 0;
822  for(unsigned int ivs = 0; ivs < samples.size(); ++ivs )
823  {
824  for(int is = 0; is < samples[ivs]->GetNSamples(); ++is)
825  {
826  hScanSamSplit[SampleIterator]->SetBinContent(j+1, 2*sampleSplitllh[SampleIterator]);
827  SampleIterator++;
828  }
829  }
830  }
831  }
832  for(unsigned int ivc = 0; ivc < systematics.size(); ++ivc )
833  {
834  Cov_LLH[ivc]->cd();
835  hScanCov[ivc]->Write();
836  }
837 
838  for(unsigned int ivs = 0; ivs < samples.size(); ++ivs )
839  {
840  SampleClass_LLH[ivs]->cd();
841  hScanSample[ivs]->Write();
842  }
843  Sample_LLH->cd();
844  hScanSam->Write();
845  Total_LLH->cd();
846  hScan->Write();
847 
848  if(PlotLLHScanBySample)
849  {
850  int SampleIterator = 0;
851  for(unsigned int ivs = 0; ivs < samples.size(); ++ivs )
852  {
853  for(int is = 0; is < samples[ivs]->GetNSamples(); ++is)
854  {
855  SampleSplit_LLH[SampleIterator]->cd();
856  hScanSamSplit[SampleIterator]->Write();
857  SampleIterator++;
858  }
859  }
860  }
861 
862  // Reset the parameters to their CentralValue central values
863  if (IsPCA) {
864  cov->GetPCAHandler()->SetParPropPCA(i, CentralValue);
865  } else {
866  cov->SetParProp(i, CentralValue);
867  }
868  }//end loop over systematics
869  }//end loop covariance classes
870 
871  for(unsigned int ivc = 0; ivc < systematics.size(); ++ivc )
872  {
873  Cov_LLH[ivc]->Write();
874  delete Cov_LLH[ivc];
875  }
876 
877  for(unsigned int ivs = 0; ivs < samples.size(); ++ivs )
878  {
879  SampleClass_LLH[ivs]->Write();
880  delete SampleClass_LLH[ivs];
881  }
882 
883  Sample_LLH->Write();
884  delete Sample_LLH;
885 
886  Total_LLH->Write();
887  delete Total_LLH;
888 
889  if(PlotLLHScanBySample)
890  {
891  int SampleIterator = 0;
892  for(unsigned int ivs = 0; ivs < samples.size(); ++ivs )
893  {
894  for(int is = 0; is < samples[ivs]->GetNSamples(); ++is )
895  {
896  SampleSplit_LLH[SampleIterator]->Write();
897  delete SampleSplit_LLH[SampleIterator];
898  SampleIterator++;
899  }
900  }
901  }
902 }
903 
904 // *************************
905 //LLH scan is good first estimate of step scale
906 void FitterBase::GetStepScaleBasedOnLLHScan(const std::string& outputFileName) {
907 // *************************
908 
909  TFile* outputFileLLH = nullptr;
910  bool ownsfile = false;
911 
912  if(outputFileName != ""){
913  //If filename matches that in the config file, get ownership issues. Easier just to run LLH scan again with correct ownership
914  if (outputFile != nullptr && std::string(outputFile->GetName()) == outputFileName) {
915  outputFileLLH = outputFile;
916  }
917  else {
918  outputFileLLH = M3::Open(outputFileName, "READ", __FILE__, __LINE__);
919  ownsfile = true;
920  }
921  }
922  else if (outputFile != nullptr)
923  outputFileLLH = outputFile;
924 
925  MACH3LOG_INFO("Starting Get Step Scale Based On LLHScan");
926 
927  auto ParamVector = GetFromManager<std::vector<std::string>>(fitMan->raw()["LLHScan"]["StepScaleParameters"], {}, __FILE__ , __LINE__);
928 
929  std::string LLH_type = "Sample_LLH";
930  const std::string llhType = GetFromManager<std::string>(fitMan->raw()["LLHScan"]["LLHType"], {}, __FILE__ , __LINE__);
931  if(!llhType.empty()){
932  LLH_type = llhType;
933  }
934 
935  TDirectory *LLHScans = nullptr;
936  if(outputFileLLH != nullptr)
937  LLHScans = outputFileLLH->Get<TDirectory>(LLH_type.c_str());
938 
939  if(!LLHScans || LLHScans->IsZombie() || LLHScans == nullptr)
940  {
941  MACH3LOG_WARN("Couldn't find LLH directory, it looks like LLH scan wasn't run, will do this now");
942  RunLLHScan();
943  outputFileLLH = outputFile;
944  LLHScans = outputFileLLH->Get<TDirectory>(LLH_type.c_str());
945  }
946 
947  for (ParameterHandlerBase *cov : systematics)
948  {
949  MACH3LOG_INFO("Step scale before optimisation");
950  cov->PrintIndivStepScale();
951  const int npars = cov->GetNumParams();
952 
953  // Vector of parameter names correlated to given parameter, from correlation matrix
954  std::vector<std::vector<std::string>> CorrParams(npars);
955  std::vector<double> StepScale(npars);
956  TMatrixDSym *cov_matrix = cov->GetCovMatrix();
957 
958  for (int i = 0; i < npars; ++i)
959  {
960  std::string name = cov->GetParFancyName(i);
961 
962  //Make vector of parameters which are correlated to given parameter
963  std::map<std::string, double> parCorr;
964  for (int j = 0; j < npars; ++j) {
965  double correlation = (*cov_matrix)(i, j) / std::sqrt((*cov_matrix)(i, i) * (*cov_matrix)(j, j));
966  std::string par_corr_name = cov->GetParFancyName(j);
967  parCorr[par_corr_name] = correlation;
968  }
969 
970  for (const auto& corrMap : parCorr){
971  std::string corr_var_name = corrMap.first;
972  if (!ParamVector.empty()){
973  if (std::find(ParamVector.begin(), ParamVector.end(), name) == ParamVector.end()) {
974  // Only create vector if parameter is in chosen list from config file
975  continue;
976  }
977  if (std::find(ParamVector.begin(), ParamVector.end(), corr_var_name) == ParamVector.end()) {
978  // Only consider correlation of parameters in chosen list
979  continue;
980  }
981 
982  }
983  double corr = corrMap.second;
984  int index = cov->GetParIndex(corr_var_name);
985  if(index == i) continue;
986  // Cut on what is consider a correlated parameter
987  // Also only allow same groups correlations
988  std::string parameter_group_i = "";
989  std::string parameter_group_index = "";
990  //If type isnt specified, allow parameters of different groups to be correlated
991  if (auto *param_handler= dynamic_cast<ParameterHandlerGeneric *>(cov)) {
992  parameter_group_i = param_handler->GetParamGroup(i);
993  parameter_group_index = param_handler->GetParamGroup(index);
994  }
995  if(std::abs(corr) > 0.3 && parameter_group_i == parameter_group_index)
996  CorrParams[i].push_back(corr_var_name);
997  }
998  StepScale[i] = cov->GetIndivStepScale(i);
999 
1000  if (!ParamVector.empty()){
1001  if (std::find(ParamVector.begin(), ParamVector.end(), name) == ParamVector.end()) {
1002  // 'name' is not in the vector, skip this iteration
1003  continue;
1004  }
1005  }
1006 
1007  TH1D* LLHScan = nullptr;
1008  if(LLH_type == "Total_LLH")
1009  LLHScan = LLHScans->Get<TH1D>((name+"_full").c_str());
1010  else
1011  LLHScan = LLHScans->Get<TH1D>((name+"_sam").c_str());
1012 
1013  if(LLHScan == nullptr)
1014  {
1015  MACH3LOG_WARN("Couldn't find LLH scan, for {}, skipping", name);
1016  continue;
1017  }
1018  const double LLH_val = std::max(LLHScan->GetBinContent(1), LLHScan->GetBinContent(LLHScan->GetNbinsX()));
1019  //If there is no sensitivity leave it
1020  if(LLH_val < 0.001) continue;
1021 
1022  // EM: assuming that the likelihood is gaussian, approximate sigma value is given by variation/sqrt(-2LLH)
1023  // can evaluate this at any point, simple to evaluate it in the first bin of the LLH scan
1024  // KS: We assume variation is 1 sigma, each dial has different scale so it becomes faff...
1025  const double Var = 1.;
1026  const double approxSigma = std::abs(Var)/std::sqrt(LLH_val);
1027  const double GlobalScale = cov->GetGlobalStepScale();
1028  // Based on Ewan comment I just took the 1sigma width from the LLH, assuming it was Gaussian, but then had to also scale by 2.38/sqrt(N_params)
1029  const double TargetStep = approxSigma * 2.38 / std::sqrt(npars);
1030  // KS: Need to divide by currently used gloalStepScale
1031  const double NewStepScale = TargetStep / GlobalScale;
1032 
1033  StepScale[i] = NewStepScale;
1034  MACH3LOG_DEBUG("Sigma: {}", approxSigma);
1035  MACH3LOG_DEBUG("Target Step Size (before accounting for global step size): {}", TargetStep);
1036  MACH3LOG_DEBUG("Optimal Step Size: {}", NewStepScale);
1037  }
1038  std::vector<double> StepScaleCorr = StepScale;
1039  for (int p = 0; p < npars; p++){
1040  //Adjust parameter step scale if correlated parameters exist
1041  if(CorrParams[p].size() != 0){
1042  double avg_step_scale = StepScale[p];
1043  for(const auto& corrName : CorrParams[p]) {
1044  int index = cov->GetParIndex(corrName);
1045  avg_step_scale += StepScale[index];
1046  }
1047  //Determine average step scale for correlated parameters
1048  avg_step_scale /= static_cast<double>(CorrParams[p].size()) + 1.0;
1049  StepScaleCorr[p] = avg_step_scale;
1050  MACH3LOG_INFO("Changed step scale of parameter {} from {} to {}",cov->GetParFancyName(p),StepScale[p],StepScaleCorr[p]);
1051  }
1052  }
1053  MACH3LOG_INFO("Step scale after optimisation");
1054  cov->SetIndivStepScale(StepScaleCorr);
1055  cov->SaveUpdatedMatrixConfig();
1056  }
1057  if(ownsfile && outputFileLLH != nullptr) delete outputFileLLH;
1058 }
1059 // *************************
1060 // Run 2D LLH scan
1062 // *************************
1063  // Save the settings into the output file
1064  SaveSettings();
1065 
1066  MACH3LOG_INFO("Starting 2D LLH Scan");
1067 
1068  TDirectory *Sample_2DLLH = outputFile->mkdir("Sample_2DLLH");
1069  auto SkipVector = GetFromManager<std::vector<std::string>>(fitMan->raw()["LLHScan"]["LLHScanSkipVector"], {}, __FILE__ , __LINE__);;
1070 
1071  //Do we want a logarithmic LLH scan
1072  bool LLHLogarithmic = GetFromManager<bool>(fitMan->raw()["LLHScan"]["LLHLogarithmic"], false, __FILE__ , __LINE__);
1073 
1074  // Number of points we do for each LLH scan
1075  const int n_points = GetFromManager<int>(fitMan->raw()["LLHScan"]["2DLLHScanPoints"], 20, __FILE__ , __LINE__);
1076  // We print 5 reweights
1077  const int countwidth = int(double(n_points)/double(5));
1078 
1079  // Loop over the covariance classes
1080  for (ParameterHandlerBase *cov : systematics)
1081  {
1082  // Scan over all the parameters
1083  // Get the number of parameters
1084  int npars = cov->GetNumParams();
1085  bool IsPCA = cov->IsPCA();
1086  if (IsPCA) npars = cov->GetNParameters();
1087 
1088  for (int i = 0; i < npars; ++i)
1089  {
1090  std::string name_x = cov->GetParFancyName(i);
1091  if (IsPCA) name_x += "_PCA";
1092  // Get the parameter central and bounds
1093  double central_x, lower_x, upper_x;
1094  GetParameterScanRange(cov, i, central_x, lower_x, upper_x, n_points, "X");
1095  std::vector<double> binEdges_x;
1096 
1097  //Logarithmic scan
1098  if(LLHLogarithmic){
1099  if (lower_x < 0.0 || upper_x < 0.0){
1100  MACH3LOG_WARN("Cannot perform logarithmic scan for {} "" with range [{}, {}], falling back to linear scan", name_x, lower_x, upper_x);
1101  } else {
1102  binEdges_x = CalculateBinEdges(lower_x, upper_x, n_points);
1103  }
1104  }
1105  // KS: Check if we want to skip this parameter
1106  if(CheckSkipParameter(SkipVector, name_x)) continue;
1107 
1108  for (int j = 0; j < i; ++j)
1109  {
1110  std::string name_y = cov->GetParFancyName(j);
1111  if (IsPCA) name_y += "_PCA";
1112  // KS: Check if we want to skip this parameter
1113  if(CheckSkipParameter(SkipVector, name_y)) continue;
1114 
1115  std::unique_ptr<TH2D> hScanSam;
1116  // Get the parameter central and bounds
1117  double central_y, lower_y, upper_y;
1118  GetParameterScanRange(cov, j, central_y, lower_y, upper_y, n_points, "Y");
1119  if(LLHLogarithmic){
1120  //negative values break it
1121  if (lower_x < 0.0 || upper_x < 0.0 || lower_y < 0.0 || upper_y < 0.0 ) {
1122  MACH3LOG_WARN("Cannot perform logarithmic scan for {} and {} "" with range [{}, {}], [{}, {}], falling back to linear scan", name_x, lower_x, upper_x, name_y, lower_y, upper_y);
1123  hScanSam = std::make_unique<TH2D>((name_x + "_" + name_y + "_sam").c_str(), (name_x + "_" + name_y + "_sam").c_str(), n_points, lower_x, upper_x, n_points, lower_y, upper_y);
1124  hScanSam->SetDirectory(nullptr);
1125  hScanSam->GetXaxis()->SetTitle(name_x.c_str());
1126  hScanSam->GetYaxis()->SetTitle(name_y.c_str());
1127  hScanSam->GetZaxis()->SetTitle("2LLH_sam");
1128  } else {
1129  auto binEdges_y = CalculateBinEdges(lower_y, upper_y, n_points);
1130  hScanSam = std::make_unique<TH2D>((name_x + "_" + name_y + "_sam").c_str(), (name_x + "_" + name_y + "_sam").c_str(), n_points, binEdges_x.data(), n_points, binEdges_y.data());
1131  }
1132  }
1133  //If not then just do the normal thing
1134  else {
1135  hScanSam = std::make_unique<TH2D>((name_x + "_" + name_y + "_sam").c_str(), (name_x + "_" + name_y + "_sam").c_str(), n_points, lower_x, upper_x, n_points, lower_y, upper_y);
1136  hScanSam->SetDirectory(nullptr);
1137  hScanSam->GetXaxis()->SetTitle(name_x.c_str());
1138  hScanSam->GetYaxis()->SetTitle(name_y.c_str());
1139  hScanSam->GetZaxis()->SetTitle("2LLH_sam");
1140  }
1141 
1142  // Scan over the parameter space
1143  for (int x = 0; x < n_points; ++x)
1144  {
1145  if (x % countwidth == 0)
1146  M3::Utils::PrintProgressBar(x, n_points);
1147 
1148  for (int y = 0; y < n_points; ++y)
1149  {
1150  // For PCA we have to do it differently
1151  if (IsPCA) {
1152  cov->GetPCAHandler()->SetParPropPCA(i, hScanSam->GetXaxis()->GetBinCenter(x+1));
1153  cov->GetPCAHandler()->SetParPropPCA(j, hScanSam->GetYaxis()->GetBinCenter(y+1));
1154  } else {
1155  // Set the parameter
1156  cov->SetParProp(i, hScanSam->GetXaxis()->GetBinCenter(x+1));
1157  cov->SetParProp(j, hScanSam->GetYaxis()->GetBinCenter(y+1));
1158  }
1159  // Reweight the MC
1160  for(unsigned int ivs = 0; ivs < samples.size(); ++ivs) {
1161  samples[ivs]->Reweight();
1162  }
1163 
1164  // Get the -log L likelihoods
1165  double samplellh = 0;
1166  for(unsigned int ivs = 0; ivs < samples.size(); ++ivs) {
1167  samplellh += samples[ivs]->GetLikelihood();
1168  }
1169  hScanSam->SetBinContent(x+1, y+1, 2*samplellh);
1170  }// end loop over y points
1171  } // end loop over x points
1172 
1173  Sample_2DLLH->cd();
1174  hScanSam->Write();
1175  // Reset the parameters to their central central values
1176  if (IsPCA) {
1177  cov->GetPCAHandler()->SetParPropPCA(i, central_x);
1178  cov->GetPCAHandler()->SetParPropPCA(j, central_y);
1179  } else {
1180  cov->SetParProp(i, central_x);
1181  cov->SetParProp(j, central_y);
1182  }
1183  } //end loop over systematics y
1184  }//end loop over systematics X
1185  }//end loop covariance classes
1186  Sample_2DLLH->Write();
1187  delete Sample_2DLLH;
1188 }
1189 
1190 // *************************
1191 // Run a general multi-dimensional LLH scan
1193 // *************************
1194  // Save the settings into the output file
1195  SaveSettings();
1196 
1197  MACH3LOG_INFO("Starting {}", __func__);
1198 
1199  //KS: Turn it on if you want LLH scan for each ND sample separately, which increase time significantly but can be useful for validating new samples or dials.
1200  bool PlotLLHScanBySample = GetFromManager<bool>(fitMan->raw()["LLHScan"]["LLHScanBySample"], false, __FILE__ , __LINE__);
1201  auto ParamsOfInterest = GetFromManager<std::vector<std::string>>(fitMan->raw()["LLHScan"]["LLHParameters"], {}, __FILE__, __LINE__);
1202 
1203  if(ParamsOfInterest.empty()) {
1204  MACH3LOG_WARN("There were no LLH parameters of interest specified to run the LLHMap! LLHMap will not run at all ...");
1205  return;
1206  }
1207 
1208  // Parameters IDs within the covariance objects
1209  // ParamsCovIDs = {Name, CovObj, IDinCovObj}
1210  std::vector<std::tuple<std::string, ParameterHandlerBase*, int>> ParamsCovIDs;
1211  for(auto& p : ParamsOfInterest) {
1212  bool found = false;
1213  for(auto cov : systematics) {
1214  for(int c = 0; c < cov->GetNumParams(); ++c) {
1215  if(cov->GetParName(c) == p || cov->GetParFancyName(c) == p) {
1216  bool add = true;
1217  for(auto& pc : ParamsCovIDs) {
1218  if(std::get<1>(pc) == cov && std::get<2>(pc) == c)
1219  {
1220  MACH3LOG_WARN("Parameter {} as {}({}) listed multiple times for LLHMap, omitting and using only once!", p, cov->GetName(), c);
1221  add = false;
1222  break;
1223  }
1224  }
1225 
1226  if(add)
1227  ParamsCovIDs.push_back(std::make_tuple(p, cov, c));
1228 
1229  found = true;
1230  break;
1231  }
1232  }
1233  if(found)
1234  break;
1235  }
1236  if(found)
1237  MACH3LOG_INFO("Parameter {} found in {} at an index {}.", p, std::get<1>(ParamsCovIDs.back())->GetName(), std::get<2>(ParamsCovIDs.back()));
1238  else
1239  MACH3LOG_WARN("Parameter {} not found in any of the systematic covariance objects. Will not scan over this one!", p);
1240  }
1241 
1242  // ParamsRanges["parameter"] = {nPoints, {low, high}}
1243  std::map<std::string, std::pair<int, std::pair<double, double>>> ParamsRanges;
1244 
1245  MACH3LOG_INFO("======================================================================================");
1246  MACH3LOG_INFO("Performing a general multi-dimensional LogL map scan over following parameters ranges:");
1247  MACH3LOG_INFO("======================================================================================");
1248  unsigned long TotalPoints = 1;
1249 
1250  double nSigma = GetFromManager<int>(fitMan->raw()["LLHScan"]["LLHScanSigma"], 1., __FILE__, __LINE__);
1251 
1252  // TN: Setting up the scan ranges might look like a re-implementation of the
1253  // FitterBase::GetScanRange, but I guess the use-case here is a bit different.
1254  // Anyway, just in case, we can discuss and rewrite to everyone's liking!
1255  for(auto& p : ParamsCovIDs) {
1256  // Auxiliary vars to help readability
1257  std::string name = std::get<0>(p);
1258  int i = std::get<2>(p);
1259  ParameterHandlerBase* cov = std::get<1>(p);
1260 
1261  ParamsRanges[name].first = GetFromManager<int>(fitMan->raw()["LLHScan"]["LLHScanPoints"], 20, __FILE__, __LINE__);
1262  if(CheckNodeExists(fitMan->raw(),"LLHScan","ScanPoints"))
1263  ParamsRanges[name].first = GetFromManager<int>(fitMan->raw()["LLHScan"]["ScanPoints"][name], ParamsRanges[name].first, __FILE__, __LINE__);
1264 
1265  // Get the parameter priors and bounds
1266  double CentralValue = cov->GetParProp(i);
1267 
1268  bool IsPCA = cov->IsPCA();
1269  if (IsPCA)
1270  CentralValue = cov->GetPCAHandler()->GetParPropPCA(i);
1271 
1272  double prior = cov->GetParPreFit(i);
1273  if (IsPCA)
1274  prior = cov->GetPCAHandler()->GetPreFitValuePCA(i);
1275 
1276  if (std::abs(CentralValue - prior) > 1e-10) {
1277  MACH3LOG_INFO("For {} scanning around value {} rather than prior {}", name, CentralValue, prior);
1278  }
1279  // Get the covariance matrix and do the +/- nSigma
1280  // Set lower and upper bounds relative the CentralValue
1281  // Set the parameter ranges between which LLH points are scanned
1282  double lower = CentralValue - nSigma*cov->GetDiagonalError(i);
1283  double upper = CentralValue + nSigma*cov->GetDiagonalError(i);
1284  // If PCA, transform these parameter values to the PCA basis
1285  if (IsPCA) {
1286  lower = CentralValue - nSigma*std::sqrt((cov->GetPCAHandler()->GetEigenValues())(i));
1287  upper = CentralValue + nSigma*std::sqrt((cov->GetPCAHandler()->GetEigenValues())(i));
1288  MACH3LOG_INFO("eval {} = {:.2f}", i, cov->GetPCAHandler()->GetEigenValues()(i));
1289  MACH3LOG_INFO("CV {} = {:.2f}", i, CentralValue);
1290  MACH3LOG_INFO("lower {} = {:.2f}", i, lower);
1291  MACH3LOG_INFO("upper {} = {:.2f}", i, upper);
1292  MACH3LOG_INFO("nSigma = {:.2f}", nSigma);
1293  }
1294 
1295  ParamsRanges[name].second = {lower,upper};
1296 
1297  if(CheckNodeExists(fitMan->raw(),"LLHScan","ScanRanges"))
1298  ParamsRanges[name].second = GetFromManager<std::pair<double,double>>(fitMan->raw()["LLHScan"]["ScanRanges"][name], ParamsRanges[name].second, __FILE__, __LINE__);
1299 
1300  MACH3LOG_INFO("{} from {:.4f} (lower bin edge) to {:.4f} (upper bin edge) with a {:.5f} step ({} points total)",
1301  name, ParamsRanges[name].second.first, ParamsRanges[name].second.second,
1302  (ParamsRanges[name].second.second - ParamsRanges[name].second.first)/(ParamsRanges[name].first),
1303  ParamsRanges[name].first);
1304 
1305  TotalPoints *= ParamsRanges[name].first;
1306  }
1307 
1308  // TN: Waiting for C++ 20 std::format() function
1309  MACH3LOG_INFO("In total, looping over {} points, from {} parameters. Estimates for run time:", TotalPoints, ParamsCovIDs.size());
1310  MACH3LOG_INFO(" 1 s per point = {:.4f} hours", double(TotalPoints)/3600.);
1311  MACH3LOG_INFO(" 0.1 s per point = {:.4f} hours", double(TotalPoints)/36000.);
1312  MACH3LOG_INFO("0.01 s per point = {:.4f} hours", double(TotalPoints)/360000.);
1313  MACH3LOG_INFO("==================================================================================");
1314 
1315  const int countwidth = int(double(TotalPoints)/double(20));
1316 
1317  // Tree to store LogL values
1318  auto LLHMap = new TTree("llhmap", "LLH Map");
1319 
1320  std::vector<double> CovLogL(systematics.size());
1321  for(unsigned int ivc = 0; ivc < systematics.size(); ++ivc )
1322  {
1323  std::string NameTemp = systematics[ivc]->GetName();
1324  NameTemp = NameTemp.substr(0, NameTemp.find("_cov")) + "_LLH";
1325  LLHMap->Branch(NameTemp.c_str(), &CovLogL[ivc]);
1326  }
1327 
1328  std::vector<double> SampleClassLogL(samples.size());
1329  for(unsigned int ivs = 0; ivs < samples.size(); ++ivs )
1330  {
1331  std::string NameTemp = samples[ivs]->GetName()+"_LLH";
1332  LLHMap->Branch(NameTemp.c_str(), &SampleClassLogL[ivs]);
1333  }
1334 
1335  double SampleLogL, TotalLogL;
1336  LLHMap->Branch("Sample_LLH", &SampleLogL);
1337  LLHMap->Branch("Total_LLH", &TotalLogL);
1338 
1339  std::vector<double>SampleSplitLogL;
1340  if(PlotLLHScanBySample)
1341  {
1342  SampleSplitLogL.resize(TotalNSamples);
1343  int SampleIterator = 0;
1344  for(unsigned int ivs = 0; ivs < samples.size(); ++ivs )
1345  {
1346  for(int is = 0; is < samples[ivs]->GetNSamples(); ++is )
1347  {
1348  std::string NameTemp =samples[ivs]->GetSampleTitle(is)+"_LLH";
1349  LLHMap->Branch(NameTemp.c_str(), &SampleSplitLogL[SampleIterator]);
1350  SampleIterator++;
1351  }
1352  }
1353  }
1354 
1355  std::vector<double> ParamsValues(ParamsCovIDs.size());
1356  for(unsigned int i=0; i < ParamsCovIDs.size(); ++i)
1357  LLHMap->Branch(std::get<0>(ParamsCovIDs[i]).c_str(), &ParamsValues[i]);
1358 
1359  // Setting up the scan
1360  // Starting at index {0,0,0,...}
1361  std::vector<unsigned long> idx(ParamsCovIDs.size(), 0);
1362 
1363  // loop over scanned points sp
1364  for(unsigned long sp = 0; sp < TotalPoints; ++sp)
1365  {
1366  // At each point need to find the indices and test values to calculate LogL
1367  for(unsigned int n = 0; n < ParamsCovIDs.size(); ++n)
1368  {
1369  // Auxiliaries
1370  std::string name = std::get<0>(ParamsCovIDs[n]);
1371  int points = ParamsRanges[name].first;
1372  double low = ParamsRanges[name].second.first;
1373  double high = ParamsRanges[name].second.second;
1374 
1375  // Find the n-th index of the sp-th scanned point
1376  unsigned long dev = 1;
1377  for(unsigned int m = 0; m <= n; ++m)
1378  dev *= ParamsRanges[std::get<0>(ParamsCovIDs[m])].first;
1379 
1380  idx[n] = sp % dev;
1381  if (n > 0)
1382  idx[n] = idx[n] / ( dev / points );
1383 
1384  // Parameter test value = low + ( high - low ) * idx / #points
1385  ParamsValues[n] = low + (2 * double(idx[n]) + 1) * (high-low) / (2 * double(points));
1386 
1387  // Now set the covariance objects
1388  // Auxiliary
1389  ParameterHandlerBase* cov = std::get<1>(ParamsCovIDs[n]);
1390  int i = std::get<2>(ParamsCovIDs[n]);
1391 
1392  if(cov->IsPCA())
1393  cov->GetPCAHandler()->SetParPropPCA(i, ParamsValues[n]);
1394  else
1395  cov->SetParProp(i, ParamsValues[n]);
1396  }
1397 
1398  // Reweight samples and calculate LogL
1399  TotalLogL = .0;
1400  SampleLogL = .0;
1401 
1402  for(unsigned int ivs = 0; ivs < samples.size(); ++ivs)
1403  samples[ivs]->Reweight();
1404 
1405  for(unsigned int ivs = 0; ivs < samples.size(); ++ivs)
1406  {
1407  SampleClassLogL[ivs] = 2.*samples[ivs]->GetLikelihood();
1408  SampleLogL += SampleClassLogL[ivs];
1409  }
1410  TotalLogL += SampleLogL;
1411 
1412  // CovObjs LogL
1413  for(unsigned int ivc = 0; ivc < systematics.size(); ++ivc )
1414  {
1415  CovLogL[ivc] = 2.*systematics[ivc]->GetLikelihood();
1416  TotalLogL += CovLogL[ivc];
1417  }
1418 
1419  if(PlotLLHScanBySample)
1420  {
1421  int SampleIterator = 0;
1422  for(unsigned int ivs = 0; ivs < samples.size(); ++ivs )
1423  {
1424  for(int is = 0; is < samples[ivs]->GetNSamples(); ++is)
1425  {
1426  SampleSplitLogL[SampleIterator] = 2.*samples[ivs]->GetSampleLikelihood(is);
1427  SampleIterator++;
1428  }
1429  }
1430  }
1431 
1432  LLHMap->Fill();
1433 
1434  if (sp % countwidth == 0)
1435  M3::Utils::PrintProgressBar(sp, TotalPoints);
1436  }
1437 
1438  outputFile->cd();
1439  LLHMap->Write();
1440 }
1441 
1442 // *************************
1443 // For comparison with P-Theta we usually have to apply different parameter values then usual 1, 3 sigma
1444 void FitterBase::CustomRange(const std::string& ParName, const double sigma, double& ParamShiftValue) const {
1445 // *************************
1446  if(!fitMan->raw()["SigmaVar"]["CustomRange"]) return;
1447 
1448  auto Config = fitMan->raw()["SigmaVar"]["CustomRange"];
1449 
1450  const auto sigmaStr = std::to_string(static_cast<int>(std::round(sigma)));
1451 
1452  if (Config[ParName] && Config[ParName][sigmaStr]) {
1453  ParamShiftValue = Config[ParName][sigmaStr].as<double>();
1454  MACH3LOG_INFO(" ::: setting custom range from config ::: {} -> {}", ParName, ParamShiftValue);
1455  }
1456 }
1457 
1458 // *************************
1460 void WriteHistograms(TH1 *hist, const std::string& baseName) {
1461 // *************************
1462  if (!hist) return;
1463  hist->SetTitle(baseName.c_str());
1464  // Get the class name of the histogram
1465  TString className = hist->ClassName();
1466 
1467  // Set the appropriate axis title based on the histogram type
1468  if (className.Contains("TH1")) {
1469  hist->GetYaxis()->SetTitle("Events");
1470  } else if (className.Contains("TH2")) {
1471  hist->GetZaxis()->SetTitle("Events");
1472  }
1473  hist->Write(baseName.c_str());
1474 }
1475 
1476 // *************************
1479  const std::string& suffix,
1480  const bool by_mode,
1481  const bool by_channel,
1482  const std::vector<TDirectory*>& SampleDir) {
1483 // *************************
1484  MaCh3Modes *modes = sample->GetMaCh3Modes();
1485  for (int iSample = 0; iSample < sample->GetNSamples(); ++iSample) {
1486  SampleDir[iSample]->cd();
1487  const std::string sampleName = sample->GetSampleTitle(iSample);
1488  for(int iDim1 = 0; iDim1 < sample->GetNDim(iSample); iDim1++) {
1489  std::string ProjectionName = sample->GetKinVarName(iSample, iDim1);
1490  std::string ProjectionSuffix = "_1DProj" + std::to_string(iDim1);
1491 
1492  // Probably a better way of handling this logic
1493  if (by_mode) {
1494  for (int iMode = 0; iMode < modes->GetNModes(); ++iMode) {
1495  auto modeHist = sample->Get1DVarHistByModeAndChannel(iSample, ProjectionName, iMode);
1496  WriteHistograms(modeHist.get(), sampleName + "_" + modes->GetMaCh3ModeName(iMode) + ProjectionSuffix + suffix);
1497  }
1498  }
1499 
1500  if (by_channel) {
1501  for (int iChan = 0; iChan < sample->GetNOscChannels(iSample); ++iChan) {
1502  auto chanHist = sample->Get1DVarHistByModeAndChannel(iSample, ProjectionName, -1, iChan); // -1 skips over mode plotting
1503  WriteHistograms(chanHist.get(), sampleName + "_" + sample->GetFlavourName(iSample, iChan) + ProjectionSuffix + suffix);
1504  }
1505  }
1506 
1507  if (by_mode && by_channel) {
1508  for (int iMode = 0; iMode < modes->GetNModes(); ++iMode) {
1509  for (int iChan = 0; iChan < sample->GetNOscChannels(iSample); ++iChan) {
1510  auto hist = sample->Get1DVarHistByModeAndChannel(iSample, ProjectionName, iMode, iChan);
1511  WriteHistograms(hist.get(), sampleName + "_" + modes->GetMaCh3ModeName(iMode) + "_" + sample->GetFlavourName(iSample, iChan) + ProjectionSuffix + suffix);
1512  }
1513  }
1514  }
1515 
1516  if (!by_mode && !by_channel) {
1517  auto hist = sample->Get1DVarHist(iSample, ProjectionName);
1518  WriteHistograms(hist.get(), sampleName + ProjectionSuffix + suffix);
1519  // Only for 2D and Beyond
1520  for (int iDim2 = iDim1 + 1; iDim2 < sample->GetNDim(iSample); ++iDim2) {
1521  // Get the names for the two dimensions
1522  std::string XVarName = sample->GetKinVarName(iSample, iDim1);
1523  std::string YVarName = sample->GetKinVarName(iSample, iDim2);
1524 
1525  // Get the 2D histogram for this pair
1526  auto hist2D = sample->Get2DVarHist(iSample, XVarName, YVarName);
1527 
1528  // Write the histogram
1529  std::string suffix2D = "_2DProj_" + std::to_string(iDim1) + "_vs_" + std::to_string(iDim2) + suffix;
1530  WriteHistograms(hist2D.get(), sampleName + suffix2D);
1531  }
1532  }
1533  }
1534  }
1535 }
1536 
1537 // *************************
1539 // *************************
1540  // Save the settings into the output file
1541  SaveSettings();
1542 
1543  bool plot_by_mode = GetFromManager<bool>(fitMan->raw()["SigmaVar"]["PlotByMode"], false, __FILE__ , __LINE__);
1544  bool plot_by_channel = GetFromManager<bool>(fitMan->raw()["SigmaVar"]["PlotByChannel"], false, __FILE__ , __LINE__);
1545  auto SkipVector = GetFromManager<std::vector<std::string>>(fitMan->raw()["SigmaVar"]["SkipVector"], {}, __FILE__ , __LINE__);
1546 
1547  if (plot_by_mode) MACH3LOG_INFO("Plotting by sample and mode");
1548  if (plot_by_channel) MACH3LOG_INFO("Plotting by sample and channel");
1549  if (!plot_by_mode && !plot_by_channel) MACH3LOG_INFO("Plotting by sample only");
1550  if (plot_by_mode && plot_by_channel) MACH3LOG_INFO("Plotting by sample, mode and channel");
1551 
1552  auto SigmaArray = GetFromManager<std::vector<double>>(fitMan->raw()["SigmaVar"]["SigmaArray"], {-3, -1, 0, 1, 3}, __FILE__ , __LINE__);
1553  if (std::find(SigmaArray.begin(), SigmaArray.end(), 0.0) == SigmaArray.end()) {
1554  MACH3LOG_ERROR(":: SigmaArray does not contain 0! Current contents: {} ::", fmt::join(SigmaArray, ", "));
1555  throw MaCh3Exception(__FILE__, __LINE__);
1556  }
1557 
1558  TDirectory* SigmaDir = outputFile->mkdir("SigmaVar");
1559  outputFile->cd();
1560 
1561  for (size_t s = 0; s < systematics.size(); ++s)
1562  {
1563  for(int i = 0; i < systematics[s]->GetNumParams(); i++)
1564  {
1565  std::string ParName = systematics[s]->GetParFancyName(i);
1566  // KS: Check if we want to skip this parameter
1567  if(CheckSkipParameter(SkipVector, ParName)) continue;
1568 
1569  MACH3LOG_INFO(":: Param {} ::", systematics[s]->GetParFancyName(i));
1570 
1571  TDirectory* ParamDir = SigmaDir->mkdir(ParName.c_str());
1572  ParamDir->cd();
1573 
1574  const double ParamCentralValue = systematics[s]->GetParProp(i);
1575  const double Prior = systematics[s]->GetParPreFit(i);
1576  const double ParamLower = systematics[s]->GetLowerBound(i);
1577  const double ParamUpper = systematics[s]->GetUpperBound(i);
1578 
1579  if (std::abs(ParamCentralValue - Prior) > 1e-10) {
1580  MACH3LOG_INFO("For {} scanning around value {} rather than prior {}", ParName, ParamCentralValue, Prior);
1581  }
1582 
1583  for(unsigned int iSample = 0; iSample < samples.size(); ++iSample)
1584  {
1585  auto* MaCh3Sample = samples[iSample];
1586  std::vector<TDirectory*> SampleDir(MaCh3Sample->GetNSamples());
1587  for (int SampleIndex = 0; SampleIndex < MaCh3Sample->GetNSamples(); ++SampleIndex) {
1588  SampleDir[SampleIndex] = ParamDir->mkdir(MaCh3Sample->GetSampleTitle(SampleIndex).c_str());
1589  }
1590 
1591  for (size_t j = 0; j < SigmaArray.size(); ++j) {
1592  double sigma = SigmaArray[j];
1593 
1594  double ParamShiftValue = ParamCentralValue + sigma * std::sqrt((*systematics[s]->GetCovMatrix())(i,i));
1595  ParamShiftValue = std::max(std::min(ParamShiftValue, ParamUpper), ParamLower);
1596 
1598  CustomRange(ParName, sigma, ParamShiftValue);
1599 
1600  MACH3LOG_INFO(" - set to {:<5.2f} ({:<2} sigma shift)", ParamShiftValue, sigma);
1601  systematics[s]->SetParProp(i, ParamShiftValue);
1602 
1603  std::ostringstream valStream;
1604  valStream << std::fixed << std::setprecision(2) << ParamShiftValue;
1605  std::string valueStr = valStream.str();
1606 
1607  std::ostringstream sigmaStream;
1608  sigmaStream << std::fixed << std::setprecision(2) << std::abs(sigma);
1609  std::string sigmaStr = sigmaStream.str();
1610 
1611  std::string suffix;
1612  if (sigma == 0) {
1613  suffix = "_" + ParName + "_nom_val_" + valueStr;
1614  } else {
1615  std::string sign = (sigma > 0) ? "p" : "n";
1616  suffix = "_" + ParName + "_sig_" + sign + sigmaStr + "_val_" + valueStr;
1617  }
1618 
1619  systematics[s]->SetParProp(i, ParamShiftValue);
1620  MaCh3Sample->Reweight();
1621 
1622  WriteHistogramsByMode(MaCh3Sample, suffix, plot_by_mode, plot_by_channel, SampleDir);
1623  }
1624  for (int subSampleIndex = 0; subSampleIndex < MaCh3Sample->GetNSamples(); ++subSampleIndex) {
1625  SampleDir[subSampleIndex]->Close();
1626  delete SampleDir[subSampleIndex];
1627  }
1628  ParamDir->cd();
1629  }
1630 
1631  systematics[s]->SetParProp(i, ParamCentralValue);
1632  MACH3LOG_INFO(" - set back to CV {:<5.2f}", ParamCentralValue);
1633  MACH3LOG_INFO("");
1634  ParamDir->Close();
1635  delete ParamDir;
1636  SigmaDir->cd();
1637  } // end loop over params
1638  } // end loop over systemics
1639  SigmaDir->Close();
1640  delete SigmaDir;
1641 
1642  outputFile->cd();
1643 }
#define _MaCh3_Safe_Include_Start_
KS: Avoiding warning checking for headers.
Definition: Core.h:126
#define _MaCh3_Safe_Include_End_
void WriteHistogramsByMode(SampleHandlerInterface *sample, const std::string &suffix, const bool by_mode, const bool by_channel, const std::vector< TDirectory * > &SampleDir)
Generic histogram writer - should make main code more palatable.
void WriteHistograms(TH1 *hist, const std::string &baseName)
Helper to write histograms.
#define MACH3LOG_CRITICAL
Definition: MaCh3Logger.h:38
#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
TMacro YAMLtoTMacro(const YAML::Node &yaml_node, const std::string &name)
Convert a YAML node to a ROOT TMacro object.
Definition: YamlHelper.h:166
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
void RunLLHScan()
Perform a 1D likelihood scan.
Definition: FitterBase.cpp:622
FitterBase(Manager *const fitMan)
Constructor.
Definition: FitterBase.cpp:17
void AddSystObj(ParameterHandlerBase *cov)
This function adds a Covariance object to the analysis framework. The Covariance object will be utili...
Definition: FitterBase.cpp:300
std::string GetName() const
Get name of class.
Definition: FitterBase.h:75
double logLProp
proposed likelihood
Definition: FitterBase.h:127
bool CheckSkipParameter(const std::vector< std::string > &SkipVector, const std::string &ParamName) const
KS: Check whether we want to skip parameter using skip vector.
Definition: FitterBase.cpp:537
void ProcessMCMC()
Process MCMC output.
Definition: FitterBase.cpp:408
int accCount
counts accepted steps
Definition: FitterBase.h:131
bool OutputPrepared
Checks if output prepared not repeat some operations.
Definition: FitterBase.h:174
void SaveOutput()
Save output and close files.
Definition: FitterBase.cpp:233
TFile * outputFile
Output.
Definition: FitterBase.h:156
void SaveSettings()
Save the settings that the MCMC was run with.
Definition: FitterBase.cpp:79
unsigned int step
current state
Definition: FitterBase.h:123
void PrepareOutput()
Prepare the output file.
Definition: FitterBase.cpp:155
bool SettingsSaved
Checks if setting saved not repeat some operations.
Definition: FitterBase.h:172
double accProb
current acceptance prob
Definition: FitterBase.h:129
virtual void StartFromPreviousFit(const std::string &FitName)
Allow to start from previous fit/chain.
Definition: FitterBase.cpp:350
bool FileSaved
Checks if file saved not repeat some operations.
Definition: FitterBase.h:170
std::string AlgorithmName
Name of fitting algorithm that is being used.
Definition: FitterBase.h:177
virtual std::string GetFancyName() const
This name is to get include some special algorithm extensions.
Definition: FitterBase.h:77
std::vector< double > sample_llh
store the llh breakdowns
Definition: FitterBase.h:136
void RunSigmaVar()
Perform a 1D/2D sigma var for all samples.
std::vector< SampleHandlerInterface * > samples
Sample holder.
Definition: FitterBase.h:141
void GetParameterScanRange(const ParameterHandlerBase *cov, const int i, double &CentralValue, double &lower, double &upper, const int n_points, const std::string &suffix="") const
Helper function to get parameter scan range, central value.
Definition: FitterBase.cpp:543
double stepTime
Time of single step.
Definition: FitterBase.h:153
std::unique_ptr< TStopwatch > clock
tells global time how long fit took
Definition: FitterBase.h:149
Manager * fitMan
The manager for configuration handling.
Definition: FitterBase.h:120
unsigned int stepStart
step start, by default 0 if we start from previous chain then it will be different
Definition: FitterBase.h:133
bool GetScanRange(std::map< std::string, std::vector< double >> &scanRanges) const
YSP: Set up a mapping to store parameters with user-specified ranges, suggested by D....
Definition: FitterBase.cpp:517
std::unique_ptr< TStopwatch > stepClock
tells how long single step/fit iteration took
Definition: FitterBase.h:151
TDirectory * CovFolder
Output cov folder.
Definition: FitterBase.h:158
void CustomRange(const std::string &ParName, const double sigma, double &ParamShiftValue) const
For comparison with other fitting frameworks (like P-Theta) we usually have to apply different parame...
TDirectory * SampleFolder
Output sample folder.
Definition: FitterBase.h:160
void DragRace(const int NLaps=100)
Calculates the required time for each sample or covariance object in a drag race simulation....
Definition: FitterBase.cpp:455
void Run2DLLHScan()
Perform a 2D likelihood scan.
unsigned int TotalNSamples
Total number of samples used, single SampleHandler can store more than one analysis sample!
Definition: FitterBase.h:143
double logLCurr
current likelihood
Definition: FitterBase.h:125
void RunLLHMap()
Perform a general multi-dimensional likelihood scan.
std::vector< double > syst_llh
systematic llh breakdowns
Definition: FitterBase.h:138
int auto_save
auto save every N steps
Definition: FitterBase.h:164
bool fTestLikelihood
Necessary for some fitting algorithms like PSO.
Definition: FitterBase.h:167
void GetStepScaleBasedOnLLHScan(const std::string &filename="")
LLH scan is good first estimate of step scale.
Definition: FitterBase.cpp:906
virtual ~FitterBase()
Destructor for the FitterBase class.
Definition: FitterBase.cpp:69
TTree * outTree
Output tree with posteriors.
Definition: FitterBase.h:162
void SanitiseInputs()
Remove obsolete memory and make other checks before fit starts.
Definition: FitterBase.cpp:225
void AddSampleHandler(SampleHandlerInterface *sample)
This function adds a sample PDF object to the analysis framework. The sample PDF object will be utili...
Definition: FitterBase.cpp:264
std::vector< double > CalculateBinEdges(double lowerlimit, double upperlimit, int n_points) const
Definition: FitterBase.cpp:606
std::vector< ParameterHandlerBase * > systematics
Systematic holder.
Definition: FitterBase.h:146
Class responsible for processing MCMC chains, performing diagnostics, generating plots,...
Definition: MCMCProcessor.h:60
const std::vector< TString > & GetBranchNames() const
Get the vector of branch names from root file.
void Initialise()
Scan chain, what parameters we have and load information from covariance matrices.
void GetPostfit(TVectorD *&Central, TVectorD *&Errors, TVectorD *&Central_Gauss, TVectorD *&Errors_Gauss, TVectorD *&Peaks)
Get the post-fit results (arithmetic and Gaussian)
void DrawCovariance()
Draw the post-fit covariances.
void DrawPostfit()
Draw the post-fit comparisons.
void GetCovariance(TMatrixDSym *&Cov, TMatrixDSym *&Corr)
Get the post-fit covariances and correlations.
Custom exception class used throughout MaCh3.
KS: Class describing MaCh3 modes used in the analysis, it is being initialised from config.
Definition: MaCh3Modes.h:142
int GetNModes() const
KS: Get number of modes, keep in mind actual number is +1 greater due to unknown category.
Definition: MaCh3Modes.h:155
std::string GetMaCh3ModeName(const int Index) const
KS: Get normal name of mode, if mode not known you will get UNKNOWN_BAD.
Definition: MaCh3Modes.cpp:156
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
void SaveSettings(TFile *const OutputFile) const
Add manager useful information's to TFile, in most cases to Fitter.
Definition: Manager.cpp:40
double GetParPropPCA(const int i) const
Get current parameter value using PCA.
Definition: PCAHandler.h:149
void SetParPropPCA(const int i, const double value)
Set proposed value for parameter in PCA base.
Definition: PCAHandler.h:132
double GetPreFitValuePCA(const int i) const
Get current parameter value using PCA.
Definition: PCAHandler.h:155
const TVectorD GetEigenValues() const
Get eigen values for all parameters, if you want for decomposed only parameters use GetEigenValuesMas...
Definition: PCAHandler.h:171
Base class for handling systematic uncertainty parameters.
int GetNumParams() const
Get total number of parameters.
TH2D * GetCorrelationMatrix() const
KS: Convert covariance matrix to correlation matrix and return TH2D which can be used for fancy plott...
std::string GetParName(const int i) const
Get name of parameter.
void SetParProp(const int i, const double val)
Set proposed parameter value.
double GetUpperBound(const int i) const
Get upper parameter bound in which it is physically valid.
TMatrixDSym * GetCovMatrix() const
Return covariance matrix.
std::string GetParFancyName(const int i) const
Get fancy name of the Parameter.
PCAHandler * GetPCAHandler() const
Get pointer for PCAHandler.
double GetLowerBound(const int i) const
Get lower parameter bound in which it is physically valid.
bool IsPCA() const
is PCA, can use to query e.g. LLH scans
std::string GetName() const
Get name of covariance.
double GetParPreFit(const int i) const
Get prior parameter value.
M3::float_t GetParProp(const int i) const
Get proposed parameter value.
double GetDiagonalError(const int i) const
Get diagonal error for ith parameter.
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...
Class responsible for handling implementation of samples used in analysis, reweighting and returning ...
virtual std::unique_ptr< TH1 > Get1DVarHistByModeAndChannel(const int iSample, const std::string &ProjectionVar_Str, const int kModeToFill=-1, const int kChannelToFill=-1, const int WeightStyle=0)=0
Build a 1D histogram for a given variable, optionally filtered by mode and channel.
virtual std::string GetName() const =0
Get name for Sample Handler.
virtual std::unique_ptr< TH1 > Get1DVarHist(const int iSample, const std::string &ProjectionVar, const std::vector< KinematicCut > &EventSelectionVec={}, int WeightStyle=0, const std::vector< KinematicCut > &SubEventSelectionVec={})=0
Return 1D projection of MC into given 1D variable (doesn't have to be variable used in the fit)
virtual std::string GetFlavourName(const int iSample, const int iChannel) const =0
Get the flavour name for a given sample and oscillation channel.
virtual void SaveAdditionalInfo([[maybe_unused]] TDirectory *Dir)
Store additional info in a chain.
MaCh3Modes * GetMaCh3Modes() const
Return pointer to MaCh3 modes.
virtual int GetNOscChannels(const int iSample) const =0
Get number of oscillation channels for a single sample.
virtual M3::int_t GetNSamples()
returns total number of samples
virtual std::string GetSampleTitle(const int iSample) const =0
Get fancy title for specified samples.
virtual std::string GetKinVarName(const int iSample, const int Dimension) const =0
Return Kinematic Variable name for specified sample and dimension for example "Reconstructed_Neutrino...
virtual std::unique_ptr< TH2 > Get2DVarHist(const int iSample, const std::string &ProjectionVarX, const std::string &ProjectionVarY, const std::vector< KinematicCut > &EventSelectionVec={}, const int WeightStyle=0, const std::vector< KinematicCut > &SubEventSelectionVec={})=0
Build a 2D projection of MC events into specified variables.
virtual int GetNDim(const int Sample) const =0
DB Get what dimensionality binning for given sample has.
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
void SetSeed(const std::uint64_t seed)
Set the global random seed.
Definition: Random.h:111
constexpr static const double _LARGE_LOGL_
Large Likelihood is used it parameter go out of physical boundary, this indicates in MCMC that such s...
Definition: Core.h:80
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.
int GetNThreads()
number of threads which we need for example for TRandom3
Definition: Monitor.cpp:372
bool CaseInsensitiveMatchAny(std::string Text, const std::vector< std::string > &Patterns)
Matches a string against a simple wildcard Pattern using regex. Is not case sensitive.