MaCh3  2.2.3
Reference Guide
SampleHandlerFD.cpp
Go to the documentation of this file.
1 #include "SampleHandlerFD.h"
3 #include "Manager/MaCh3Logger.h"
4 #include <cstddef>
5 
6 #include <algorithm>
7 #include <memory>
8 
9 // ************************************************
10 SampleHandlerFD::SampleHandlerFD(std::string ConfigFileName, ParameterHandlerGeneric* xsec_cov,
11  const std::shared_ptr<OscillationHandler>& OscillatorObj_) : SampleHandlerBase() {
12 // ************************************************
13  MACH3LOG_INFO("-------------------------------------------------------------------");
14  MACH3LOG_INFO("Creating SampleHandlerFD object");
15 
16  //ETA - safety feature so you can't pass a NULL xsec_cov
17  if(!xsec_cov){
18  MACH3LOG_ERROR("You've passed me a nullptr to a SystematicHandlerGeneric... I need this to setup splines!");
19  throw MaCh3Exception(__FILE__, __LINE__);
20  }
21  ParHandler = xsec_cov;
22 
23  nSamples = 1;
24 
25  if (OscillatorObj_ != nullptr) {
26  MACH3LOG_WARN("You have passed an Oscillator object through the constructor of a SampleHandlerFD object - this will be used for all oscillation channels");
27  Oscillator = OscillatorObj_;
28  }
29 
30  KinematicParameters = nullptr;
32  KinematicVectors = nullptr;
33  ReversedKinematicVectors = nullptr;
34 
35  SampleHandlerFD_array = nullptr;
36  SampleHandlerFD_data = nullptr;
37  SampleHandlerFD_array_w2 = nullptr;
38  SampleName = "";
39  SampleManager = std::make_unique<manager>(ConfigFileName.c_str());
40 
41  // Variables related to MC stat
42  FirstTimeW2 = true;
43  UpdateW2 = false;
44 }
45 
47  MACH3LOG_DEBUG("I'm deleting SampleHandlerFD");
48 
49  if (SampleHandlerFD_array != nullptr) delete[] SampleHandlerFD_array;
50  if (SampleHandlerFD_array_w2 != nullptr) delete[] SampleHandlerFD_array_w2;
51  //ETA - there is a chance that you haven't added any data...
52  if (SampleHandlerFD_data != nullptr) delete[] SampleHandlerFD_data;
53 
54  if(THStackLeg != nullptr) delete THStackLeg;
55 }
56 
58 {
59  auto ModeName = Get<std::string>(SampleManager->raw()["MaCh3ModeConfig"], __FILE__ , __LINE__);
60  Modes = std::make_unique<MaCh3Modes>(ModeName);
61  //SampleTitle has to be provided in the sample yaml otherwise this will throw an exception
62  SampleDetails.SampleTitle = Get<std::string>(SampleManager->raw()["SampleTitle"], __FILE__ , __LINE__);
63  //SampleName has to be provided in the sample yaml otherwise this will throw an exception
64  SampleName = Get<std::string>(SampleManager->raw()["SampleName"], __FILE__ , __LINE__);
65 
66  fTestStatistic = static_cast<TestStatistic>(SampleManager->GetMCStatLLH());
67  if (CheckNodeExists(SampleManager->raw(), "LikelihoodOptions")) {
68  UpdateW2 = GetFromManager<bool>(SampleManager->raw()["LikelihoodOptions"]["UpdateW2"], false);
69  }
70  //Binning
72  SampleDetails.XVarStr = GetFromManager(SampleManager->raw()["Binning"]["XVarStr"], std::string(""));
73  Binning.XBinEdges = GetFromManager(SampleManager->raw()["Binning"]["XVarBins"], std::vector<double>());
74  const auto& edgesx = Binning.XBinEdges;
75  if (!std::is_sorted(edgesx.begin(), edgesx.end())) {
76  MACH3LOG_ERROR("XVarBins must be in increasing order in sample config {}\n XVarBins: [{}]",
77  GetTitle(), fmt::join(edgesx, ", "));
78  throw MaCh3Exception(__FILE__, __LINE__);
79  }
80  if(GetXBinVarName().length() > 0){
82  } else{
83  MACH3LOG_ERROR("Please specify an X-variable string in sample config {}", SampleManager->GetFileName());
84  throw MaCh3Exception(__FILE__, __LINE__);
85  }
86 
87  SampleDetails.YVarStr = GetFromManager(SampleManager->raw()["Binning"]["YVarStr"], std::string(""));
88  Binning.YBinEdges = GetFromManager(SampleManager->raw()["Binning"]["YVarBins"], std::vector<double>());
89  const auto& edgesy = Binning.YBinEdges;
90  if (!std::is_sorted(edgesy.begin(), edgesy.end())) {
91  MACH3LOG_ERROR("YBinEdges must be in increasing order in sample config {}\n YBinEdges: [{}]",
92  GetTitle(), fmt::join(edgesy, ", "));
93  throw MaCh3Exception(__FILE__, __LINE__);
94  }
95  if(GetYBinVarName().length() > 0){
96  if(GetXBinVarName().length() == 0){
97  MACH3LOG_ERROR("Please specify an X-variable string in sample config {}. I won't work only with a Y-variable", SampleManager->GetFileName());
98  throw MaCh3Exception(__FILE__, __LINE__);
99  }
101  }
102 
103  if(GetNDim() == 0){
104  MACH3LOG_ERROR("Error setting up the sample binning");
105  MACH3LOG_ERROR("Number of dimensions is {}", GetNDim());
106  MACH3LOG_ERROR("Check that an XVarStr has been given in the sample config");
107  throw MaCh3Exception(__FILE__, __LINE__);
108  } else{
109  MACH3LOG_INFO("Found {} dimensions for sample binning", GetNDim());
110  }
111 
112  //Sanity check that some binning has been specified
113  if(Binning.XBinEdges.size() == 0 && Binning.YBinEdges.size() == 0){
114  MACH3LOG_ERROR("No binning specified for either X or Y of sample binning, please add some binning to the sample config {}", SampleManager->GetFileName());
115  throw MaCh3Exception(__FILE__, __LINE__);
116  }
117 
118  if (!CheckNodeExists(SampleManager->raw(), "BinningFile")){
119  MACH3LOG_ERROR("BinningFile not given in for sample {}, ReturnKinematicParameterBinning will not work", GetTitle());
120  throw MaCh3Exception(__FILE__, __LINE__);
121  }
122 
123  auto mtupleprefix = Get<std::string>(SampleManager->raw()["InputFiles"]["mtupleprefix"], __FILE__, __LINE__);
124  auto mtuplesuffix = Get<std::string>(SampleManager->raw()["InputFiles"]["mtuplesuffix"], __FILE__, __LINE__);
125  auto splineprefix = Get<std::string>(SampleManager->raw()["InputFiles"]["splineprefix"], __FILE__, __LINE__);
126  auto splinesuffix = Get<std::string>(SampleManager->raw()["InputFiles"]["splinesuffix"], __FILE__, __LINE__);
127 
128  int NChannels = static_cast<M3::int_t>(SampleManager->raw()["SubSamples"].size());
129  OscChannels.reserve(NChannels);
130 
131  for (auto const &osc_channel : SampleManager->raw()["SubSamples"]) {
132  std::string MTupleFileName = mtupleprefix+osc_channel["mtuplefile"].as<std::string>()+mtuplesuffix;
133 
134  OscChannelInfo OscInfo;
135  OscInfo.flavourName = osc_channel["Name"].as<std::string>();
136  OscInfo.flavourName_Latex = osc_channel["LatexName"].as<std::string>();
137  OscInfo.InitPDG = static_cast<NuPDG>(osc_channel["nutype"].as<int>());
138  OscInfo.FinalPDG = static_cast<NuPDG>(osc_channel["oscnutype"].as<int>());
139  OscInfo.ChannelIndex = GetNOscChannels();
140 
141  OscChannels.push_back(std::move(OscInfo));
142 
143  FileToInitPDGMap[MTupleFileName] = static_cast<NuPDG>(osc_channel["nutype"].as<int>());
144  FileToFinalPDGMap[MTupleFileName] = static_cast<NuPDG>(osc_channel["oscnutype"].as<int>());
145 
146  mc_files.push_back(MTupleFileName);
147  spline_files.push_back(splineprefix+osc_channel["splinefile"].as<std::string>()+splinesuffix);
148  }
149 
150  //Now grab the selection cuts from the manager
151  for ( auto const &SelectionCuts : SampleManager->raw()["SelectionCuts"]) {
152  auto TempBoundsVec = GetBounds(SelectionCuts["Bounds"]);
153  KinematicCut CutObj;
154  CutObj.LowerBound = TempBoundsVec[0];
155  CutObj.UpperBound = TempBoundsVec[1];
156  CutObj.ParamToCutOnIt = ReturnKinematicParameterFromString(SelectionCuts["KinematicStr"].as<std::string>());
157  MACH3LOG_INFO("Adding cut on {} with bounds {} to {}", SelectionCuts["KinematicStr"].as<std::string>(), TempBoundsVec[0], TempBoundsVec[1]);
158  StoredSelection.push_back(CutObj);
159  }
160 
161  // EM: initialise the mode weight map
162  for( int iMode=0; iMode < Modes->GetNModes(); iMode++ ) {
163  _modeNomWeightMap[Modes->GetMaCh3ModeName(iMode)] = 1.0;
164  }
165 
166  // EM: multiply by the nominal weight specified in the sample config file
167  if ( SampleManager->raw()["NominalWeights"] ) {
168  for( int iMode=0; iMode<Modes->GetNModes(); iMode++ ) {
169  std::string modeStr = Modes->GetMaCh3ModeName(iMode);
170  if( SampleManager->raw()["NominalWeights"][modeStr] ) {
171  double modeWeight = SampleManager->raw()["NominalWeights"][modeStr].as<double>();
172  _modeNomWeightMap[Modes->GetMaCh3ModeName(iMode)] *= modeWeight;
173  }
174  }
175  }
176 
177  // EM: print em out
178  MACH3LOG_INFO(" Nominal mode weights to apply: ");
179  for(int iMode=0; iMode<Modes->GetNModes(); iMode++ ) {
180  std::string modeStr = Modes->GetMaCh3ModeName(iMode);
181  MACH3LOG_INFO(" - {}: {}", modeStr, _modeNomWeightMap.at(modeStr));
182  }
183 }
184 
186  //First grab all the information from your sample config via your manager
188 
189  //Now initialise all the variables you will need
190  Init();
191 
193 
195  SetupFDMC();
196 
197  MACH3LOG_INFO("=============================================");
198  MACH3LOG_INFO("Total number of events is: {}", GetNEvents());
199 
201  if (OscParams.size() > 0) {
202  MACH3LOG_INFO("Setting up NuOscillator..");
203  if (Oscillator != nullptr) {
204  MACH3LOG_INFO("You have passed an OscillatorBase object through the constructor of a SampleHandlerFD object - this will be used for all oscillation channels");
205  if(Oscillator->isEqualBinningPerOscChannel() != true) {
206  MACH3LOG_ERROR("Trying to run shared NuOscillator without EqualBinningPerOscChannel, this will not work");
207  throw MaCh3Exception(__FILE__, __LINE__);
208  }
209 
210  if(OscParams.size() != Oscillator->GetOscParamsSize()){
211  MACH3LOG_ERROR("Sample {} with {} has {} osc params, while shared NuOsc has {} osc params", GetTitle(), GetSampleName(),
212  OscParams.size(), Oscillator->GetOscParamsSize());
213  MACH3LOG_ERROR("This indicate misconfiguration in your Osc yaml");
214  throw MaCh3Exception(__FILE__, __LINE__);
215  }
216  } else {
218  }
220  } else{
221  MACH3LOG_WARN("Didn't find any oscillation params, thus will not enable oscillations");
222  }
223 
224  MACH3LOG_INFO("Setting up Sample Binning..");
226  MACH3LOG_INFO("Setting up Splines..");
227  SetupSplines();
228  MACH3LOG_INFO("Setting up Normalisation Pointers..");
230  MACH3LOG_INFO("Setting up Functional Pointers..");
232  MACH3LOG_INFO("Setting up Weight Pointers..");
234  MACH3LOG_INFO("Setting up Kinematic Map..");
236  MACH3LOG_INFO("=======================================================");
237 }
238 
239 // ************************************************
241 // ************************************************
242  if(KinematicParameters == nullptr || ReversedKinematicParameters == nullptr) {
243  MACH3LOG_INFO("Map KinematicParameters or ReversedKinematicParameters hasn't been initialised");
244  throw MaCh3Exception(__FILE__, __LINE__);
245  }
246  // KS: Ensure maps exist correctly
247  for (const auto& pair : *KinematicParameters) {
248  const auto& key = pair.first;
249  const auto& value = pair.second;
250 
251  auto it = ReversedKinematicParameters->find(value);
252  if (it == ReversedKinematicParameters->end() || it->second != key) {
253  MACH3LOG_ERROR("Mismatch found: {} -> {} but {} -> {}",
254  key, value, value, (it != ReversedKinematicParameters->end() ? it->second : "NOT FOUND"));
255  throw MaCh3Exception(__FILE__, __LINE__);
256  }
257  }
258 }
259 
260 #pragma GCC diagnostic push
261 #pragma GCC diagnostic ignored "-Wconversion"
262 // ************************************************
263 void SampleHandlerFD::FillMCHist(const int Dimension) {
264 // ************************************************
265  // DB Commented out by default - Code heading towards GetLikelihood using arrays instead of root objects
266  // Wouldn't actually need this for GetLikelihood as TH objects wouldn't be filled
267  if(Dimension == 1){
268  SampleDetails._hPDF1D->Reset();
269  for (size_t yBin = 0; yBin < Binning.nYBins; ++yBin) {
270  for (size_t xBin = 0; xBin < Binning.nXBins; ++xBin) {
271  const int idx = Binning.GetBinSafe(xBin, yBin);
272  SampleDetails._hPDF1D->SetBinContent(idx + 1, SampleHandlerFD_array[idx]);
273  }
274  }
275  } else if (Dimension == 2) {
276  SampleDetails._hPDF2D->Reset();
277  for (size_t yBin = 0; yBin < Binning.nYBins; ++yBin) {
278  for (size_t xBin = 0; xBin < Binning.nXBins; ++xBin) {
279  const int idx = Binning.GetBinSafe(xBin, yBin);
280  SampleDetails._hPDF2D->SetBinContent(static_cast<int>(xBin + 1), static_cast<int>(yBin + 1), SampleHandlerFD_array[idx]);
281  }
282  }
283  } else {
284  MACH3LOG_ERROR("Asking for {} with N Dimension = {}. This is not implemented", __func__, Dimension);
285  throw MaCh3Exception(__FILE__, __LINE__);
286  }
287 }
288 #pragma GCC diagnostic pop
289 // ************************************************
290 // Function to setup the binning of your sample histograms and the underlying
291 // arrays that get handled in fillArray() and fillArray_MP().
292 // The Binning.XBinEdges are filled in the daughter class from the sample config file.
293 // This "passing" can be removed.
295 // ************************************************
296  MACH3LOG_INFO("Setting up Sample Binning");
298 
299  //A string to store the binning for a nice print out
300  std::string XBinEdgesStr = "";
301  std::string YBinEdgesStr = "";
302 
303  for(auto XBinEdge : Binning.XBinEdges){
304  XBinEdgesStr += std::to_string(XBinEdge);
305  XBinEdgesStr += ", ";
306  }
307  MACH3LOG_INFO("XBinning:");
308  MACH3LOG_INFO("{}", XBinEdgesStr);
309 
310  //And now the YBin Edges
311  for(auto YBinEdge : Binning.YBinEdges){
312  YBinEdgesStr += std::to_string(YBinEdge);
313  YBinEdgesStr += ", ";
314  }
315  MACH3LOG_INFO("YBinning:");
316  MACH3LOG_INFO("{}", YBinEdgesStr);
317 
318  //Check whether you are setting up 1D or 2D binning
319  if(GetNDim() == 1){
320  MACH3LOG_INFO("Setting up {}D binning with {}", GetNDim(), GetXBinVarName());
322  }
323  else if(GetNDim() == 2){
324  MACH3LOG_INFO("Setting up {}D binning with {} and {}", GetNDim(), GetXBinVarName(), GetYBinVarName());
326  }
327  else{
328  MACH3LOG_ERROR("Number of dimensions is not 1 or 2, this is unsupported at the moment");
329  throw MaCh3Exception(__FILE__, __LINE__);
330  }
331 }
332 
333 // ************************************************
334 bool SampleHandlerFD::IsEventSelected(const int iEvent) {
335 // ************************************************
336  const int SelectionSize = static_cast<int>(Selection.size());
337  for (int iSelection = 0; iSelection < SelectionSize; ++iSelection) {
338  const auto& Cut = Selection[iSelection];
339  const double Val = ReturnKinematicParameter(Cut.ParamToCutOnIt, iEvent);
340  if ((Val < Cut.LowerBound) || (Val >= Cut.UpperBound)) {
341  return false;
342  }
343  }
344  //DB To avoid unnecessary checks, now return false rather than setting bool to true and continuing to check
345  return true;
346 }
347 
348 // === JM Define function to check if sub-event is selected ===
349 bool SampleHandlerFD::IsSubEventSelected(const std::vector<KinematicCut> &SubEventCuts, const int iEvent, const unsigned int iSubEvent, size_t nsubevents) {
350  for (unsigned int iSelection=0;iSelection < SubEventCuts.size() ;iSelection++) {
351  std::vector<double> Vec = ReturnKinematicVector(SubEventCuts[iSelection].ParamToCutOnIt, iEvent);
352  if (nsubevents != Vec.size()) {
353  MACH3LOG_ERROR("Cannot apply kinematic cut on {} as it is of different size to plotting variable");
354  throw MaCh3Exception(__FILE__, __LINE__);
355  }
356  const double Val = Vec[iSubEvent];
357  if ((Val < SubEventCuts[iSelection].LowerBound) || (Val >= SubEventCuts[iSelection].UpperBound)) {
358  return false;
359  }
360  }
361  //DB To avoid unnecessary checks, now return false rather than setting bool to true and continuing to check
362  return true;
363 }
364 // ===========================================================
365 
366 //************************************************
367 // Reweight function
369 //************************************************
370  //KS: Reset the histograms before reweight
371  ResetHistograms();
372 
373  //You only need to do these things if Oscillator has been initialised
374  //if not then you're not considering oscillations
375  if (Oscillator) Oscillator->Evaluate();
376 
377  // Calculate weight coming from all splines if we initialised handler
378  if(SplineHandler) SplineHandler->Evaluate();
379 
380  #ifdef MULTITHREAD
381  // Call entirely different routine if we're running with openMP
382  FillArray_MP();
383  #else
384  FillArray();
385  #endif
386 
387  //KS: If you want to not update W2 wights then uncomment this line
388  if(!UpdateW2) FirstTimeW2 = false;
389 }
390 
391 //************************************************
392 // Function which does the core reweighting. This assumes that oscillation weights have
393 // already been calculated and stored in SampleHandlerFD.osc_w[iEvent]. This
394 // function takes advantage of most of the things called in setupSKMC to reduce reweighting time.
395 // It also follows the ND code reweighting pretty closely. This function fills the SampleHandlerFD
396 // array array which is binned to match the sample binning, such that bin[1][1] is the
397 // equivalent of SampleDetails._hPDF2D->GetBinContent(2,2) {Noticing the offset}
399 //************************************************
400  //DB Reset which cuts to apply
402 
404 
405  for (unsigned int iEvent = 0; iEvent < GetNEvents(); iEvent++) {
406  ApplyShifts(iEvent);
407 
408  if (!IsEventSelected(iEvent)) {
409  continue;
410  }
411 
412  FarDetectorCoreInfo* MCEvent = &MCSamples[iEvent];
413  M3::float_t splineweight = CalcWeightSpline(MCEvent);
414  //DB Catch negative spline weights and skip any event with a negative event. Previously we would set weight to zero and continue but that is inefficient. Do this on a spline-by-spline basis
415  if (splineweight <= 0.){
416  MCEvent->xsec_w = 0.;
417  continue;
418  }
419 
420  //Loop over stored normalisation and function pointers
421  M3::float_t normweight = CalcWeightNorm(MCEvent);
422 
423  // Virtual by default does nothing
424  CalcWeightFunc(iEvent);
425 
426  MCEvent->xsec_w = splineweight*normweight;
427 
428  //DB Total weight
429  M3::float_t totalweight = GetEventWeight(iEvent);
430  //DB Catch negative weights and skip any event with a negative event
431  if (totalweight <= 0.){
432  MCEvent->xsec_w = 0.;
433  continue;
434  }
435  //DB Switch on BinningOpt to allow different binning options to be implemented
436  //The alternative would be to have inheritance based on BinningOpt
437  const double XVar = *(MCEvent->x_var);
438 
439  //DB Find the relevant bin in the PDF for each event
440  const int XBinToFill = Binning.FindXBin(XVar, MCEvent->NomXBin);
441  const int YBinToFill = MCEvent->NomYBin;
442 
443  //DB Fill relevant part of thread array
444  if (XBinToFill != -1 && YBinToFill != -1) {
445  const int GlobalBin = Binning.GetBin(XBinToFill, YBinToFill);
446  SampleHandlerFD_array[GlobalBin] += totalweight;
447  if (FirstTimeW2) SampleHandlerFD_array_w2[GlobalBin] += totalweight*totalweight;
448  }
449  }
450 }
451 
452 #ifdef MULTITHREAD
453 // ************************************************
455 void SampleHandlerFD::FillArray_MP() {
456 // ************************************************
457  //DB Reset which cuts to apply
459 
461 
462  //This is stored as [y][x] due to shifts only occurring in the x variable (Erec/Lep mom) - I believe this will help reduce cache misses
463  double* SampleHandlerFD_array_private = nullptr;
464  double* SampleHandlerFD_array_private_w2 = nullptr;
465  // Declare the omp parallel region
466  // The parallel region needs to stretch beyond the for loop!
467  #pragma omp parallel private(SampleHandlerFD_array_private, SampleHandlerFD_array_private_w2)
468  {
469  // private to each thread
470  // ETA - maybe we can use parallel firstprivate to initialise these?
471  SampleHandlerFD_array_private = new double[Binning.nBins];
472  SampleHandlerFD_array_private_w2 = new double[Binning.nBins];
473 
474  std::fill_n(SampleHandlerFD_array_private, Binning.nBins, 0.0);
475  std::fill_n(SampleHandlerFD_array_private_w2, Binning.nBins, 0.0);
476 
477  //DB - Brain dump of speedup ideas
478  //
479  //Those relevant to reweighting
480  // 1. Don't bother storing and calculating NC signal events - Implemented and saves marginal s/step
481  // 2. Loop over spline event weight calculation in the following event loop - Currently done in splineSKBase->calcWeight() where multi-threading won't be optimised - Implemented and saves 0.3s/step
482  // 3. Inline getDiscVar or somehow include that calculation inside the multi-threading - Implemented and saves about 0.01s/step
483  // 4. Include isCC inside SKMCStruct so don't have to have several 'if' statements determine if oscillation weight needs to be set to 1.0 for NC events - Implemented and saves marginal s/step
484  // 5. Do explicit check on adjacent bins when finding event XBin instead of looping over all BinEdge indices - Implemented but doesn't significantly affect s/step
485  //
486  //Other aspects
487  // 1. Order minituples in Y-axis variable as this will *hopefully* reduce cache misses inside SampleHandlerFD_array_class[yBin][xBin]
488  //
489  // We will hit <0.1 s/step eventually! :D
490 
491  const unsigned int NumberOfEvents = GetNEvents();
492  #pragma omp for
493  for (unsigned int iEvent = 0; iEvent < NumberOfEvents; ++iEvent) {
494  //ETA - generic functions to apply shifts to kinematic variables
495  // Apply this before IsEventSelected is called.
496  ApplyShifts(iEvent);
497 
498  //ETA - generic functions to apply shifts to kinematic variable
499  //this is going to be slow right now due to string comps under the hood.
500  //Need to implement a more efficient version of event-by-event cut checks
501  if(!IsEventSelected(iEvent)){
502  continue;
503  }
504 
505  //DB SKDet Syst
506  //As weights were skdet::fParProp, and we use the non-shifted erec, we might as well cache the corresponding fParProp index for each event and the pointer to it
507  FarDetectorCoreInfo* MCEvent = &MCSamples[iEvent];
508  const M3::float_t splineweight = CalcWeightSpline(MCEvent);
509  //DB Catch negative spline weights and skip any event with a negative event. Previously we would set weight to zero and continue but that is inefficient
510  if (splineweight <= 0.){
511  MCEvent->xsec_w = 0.;
512  continue;
513  }
514 
515  const M3::float_t normweight = CalcWeightNorm(MCEvent);
516 
517  // Virtual by default does nothing
518  CalcWeightFunc(iEvent);
519 
520  MCEvent->xsec_w = splineweight*normweight;
521 
522  const M3::float_t totalweight = GetEventWeight(iEvent);
523 
524  //DB Catch negative weights and skip any event with a negative event
525  if (totalweight <= 0.){
526  MCEvent->xsec_w = 0.;
527  continue;
528  }
529 
530  //DB Switch on BinningOpt to allow different binning options to be implemented
531  //The alternative would be to have inheritance based on BinningOpt
532  const double XVar = (*(MCEvent->x_var));
533 
534  //DB Find the relevant bin in the PDF for each event
535  const int XBinToFill = Binning.FindXBin(XVar, MCEvent->NomXBin);
536  const int YBinToFill = MCEvent->NomYBin;
537 
538  //ETA - we can probably remove this final if check on the -1?
539  //Maybe we can add an overflow bin to the array and assign any events to this bin?
540  //Might save us an extra if call?
541  //DB Fill relevant part of thread array
542  if (XBinToFill != -1 && YBinToFill != -1) {
543  const int GlobalBin = Binning.GetBin(XBinToFill, YBinToFill);
544  SampleHandlerFD_array_private[GlobalBin] += totalweight;
545  SampleHandlerFD_array_private_w2[GlobalBin] += totalweight*totalweight;
546  }
547  }
548  //End of Calc Weights and fill Array
549  //==================================================
550  // DB Copy contents of 'SampleHandlerFD_array_private' into 'SampleHandlerFD_array' which can then be used in GetLikelihood
551  for (size_t idx = 0; idx < Binning.nBins; ++idx) {
552  #pragma omp atomic
553  SampleHandlerFD_array[idx] += SampleHandlerFD_array_private[idx];
554  if (FirstTimeW2) {
555  #pragma omp atomic
556  SampleHandlerFD_array_w2[idx] += SampleHandlerFD_array_private_w2[idx];
557  }
558  }
559 
560  delete[] SampleHandlerFD_array_private;
561  delete[] SampleHandlerFD_array_private_w2;
562  } //end of parallel region
563 }
564 #endif
565 
566 // **************************************************
567 // Helper function to reset the data and MC histograms
569 // **************************************************
570  //DB Reset values stored in PDF array to 0.
571  // Don't openMP this; no significant gain
572  #ifdef MULTITHREAD
573  #pragma omp simd
574  #endif
575  for (size_t i = 0; i < Binning.nBins; ++i) {
576  SampleHandlerFD_array[i] = 0.;
578  }
579 } // end function
580 
581 void SampleHandlerFD::RegisterIndividualFunctionalParameter(const std::string& fpName, int fpEnum, FuncParFuncType fpFunc){
582  // Add protections to not add the same functional parameter twice
583  if (funcParsNamesMap.find(fpName) != funcParsNamesMap.end()) {
584  MACH3LOG_ERROR("Functional parameter {} already registered in funcParsNamesMap with enum {}", fpName, funcParsNamesMap[fpName]);
585  throw MaCh3Exception(__FILE__, __LINE__);
586  }
587  if (std::find(funcParsNamesVec.begin(), funcParsNamesVec.end(), fpName) != funcParsNamesVec.end()) {
588  MACH3LOG_ERROR("Functional parameter {} already in funcParsNamesVec", fpName);
589  throw MaCh3Exception(__FILE__, __LINE__);
590  }
591  if (funcParsFuncMap.find(fpEnum) != funcParsFuncMap.end()) {
592  MACH3LOG_ERROR("Functional parameter enum {} already registered in funcParsFuncMap", fpEnum);
593  throw MaCh3Exception(__FILE__, __LINE__);
594  }
595  funcParsNamesMap[fpName] = fpEnum;
596  funcParsNamesVec.push_back(fpName);
597  funcParsFuncMap[fpEnum] = fpFunc;
598 }
599 
602  // RegisterFunctionalParameters is implemented in experiment-specific code,
603  // which calls RegisterIndividualFuncPar to populate funcParsNamesMap, funcParsNamesVec, and funcParsFuncMap
605  funcParsMap.resize(funcParsNamesMap.size());
606  funcParsGrid.resize(GetNEvents());
607 
608  // For every functional parameter in XsecCov that matches the name in funcParsNames, add it to the map
609  for (FunctionalParameter & fp : funcParsVec) {
610  for (std::string name : funcParsNamesVec) {
611  if (fp.name == name) {
612  MACH3LOG_INFO("Adding functional parameter: {} to funcParsMap with key: {}", fp.name, funcParsNamesMap[fp.name]);
613  fp.funcPtr = &funcParsFuncMap[funcParsNamesMap[fp.name]];
614  funcParsMap[static_cast<std::size_t>(funcParsNamesMap[fp.name])] = &fp;
615  continue;
616  }
617  }
618  // If we don't find a match, we need to throw an error
619  if (funcParsMap[static_cast<std::size_t>(funcParsNamesMap[fp.name])] == nullptr) {
620  MACH3LOG_ERROR("Functional parameter {} not found, did you define it in RegisterFunctionalParameters()?", fp.name);
621  throw MaCh3Exception(__FILE__, __LINE__);
622  }
623  }
624 
625  // Mostly the same as CalcXsecNormsBins
626  // For each event, make a vector of pointers to the functional parameters
627  for (std::size_t iEvent = 0; iEvent < static_cast<std::size_t>(GetNEvents()); ++iEvent) {
628  // Now loop over the functional parameters and get a vector of enums corresponding to the functional parameters
629  for (std::vector<FunctionalParameter>::iterator it = funcParsVec.begin(); it != funcParsVec.end(); ++it) {
630  // Check whether the interaction modes match
631  bool ModeMatch = MatchCondition((*it).modes, static_cast<int>(std::round(*(MCSamples[iEvent].mode))));
632  if (!ModeMatch) {
633  MACH3LOG_TRACE("Event {}, missed Mode check ({}) for dial {}", iEvent, *(MCSamples[iEvent].mode), (*it).name);
634  continue;
635  }
636  // Now check whether within kinematic bounds
637  bool IsSelected = true;
638  if ((*it).hasKinBounds) {
639  const auto& kinVars = (*it).KinematicVarStr;
640  const auto& selection = (*it).Selection;
641 
642  for (std::size_t iKinPar = 0; iKinPar < kinVars.size(); ++iKinPar) {
643  const double kinVal = ReturnKinematicParameter(kinVars[iKinPar], static_cast<int>(iEvent));
644 
645  bool passedAnyBound = false;
646  const auto& boundsList = selection[iKinPar];
647 
648  for (const auto& bounds : boundsList) {
649  if (kinVal > bounds[0] && kinVal <= bounds[1]) {
650  passedAnyBound = true;
651  break;
652  }
653  }
654 
655  if (!passedAnyBound) {
656  MACH3LOG_TRACE("Event {}, missed kinematic check ({}) for dial {}",
657  iEvent, kinVars[iKinPar], (*it).name);
658  IsSelected = false;
659  break;
660  }
661  }
662  }
663  // Need to then break the event loop
664  if(!IsSelected){
665  MACH3LOG_TRACE("Event {}, missed Kinematic var check for dial {}", iEvent, (*it).name);
666  continue;
667  }
668  auto funcparenum = funcParsNamesMap[(*it).name];
669  funcParsGrid.at(iEvent).push_back(funcparenum);
670  }
671  }
672  MACH3LOG_INFO("Finished setting up functional parameters");
673 }
674 
676  // Given a sample and event, apply the shifts to the event based on the vector of functional parameter enums
677  // First reset shifted array back to nominal values
678  resetShifts(iEvent);
679 
680  const std::vector<int>& fpEnums = funcParsGrid[iEvent];
681  const std::size_t nShifts = fpEnums.size();
682 
683  for (std::size_t i = 0; i < nShifts; ++i) {
684  const int fpEnum = fpEnums[i];
685  FunctionalParameter *fp = funcParsMap[static_cast<std::size_t>(fpEnum)];
686  // if (fp->funcPtr) {
687  // (*fp->funcPtr)(fp->valuePtr, iEvent);
688  // } else {
689  // MACH3LOG_ERROR("Functional parameter function pointer for {} is null for event {} in sample {}", fp->name, iEvent);
690  // throw MaCh3Exception(__FILE__, __LINE__);
691  // }
692  (*fp->funcPtr)(fp->valuePtr, iEvent);
693  }
694 }
695 
696 // ***************************************************************************
697 // Calculate the spline weight for one event
699 // ***************************************************************************
700  M3::float_t spline_weight = 1.0;
701  const int nSplines = static_cast<int>(MCEvent->xsec_spline_pointers.size());
702  //DB Xsec syst
703  //Loop over stored spline pointers
704  #ifdef MULTITHREAD
705  #pragma omp simd
706  #endif
707  for (int iSpline = 0; iSpline < nSplines; ++iSpline) {
708  spline_weight *= *(MCEvent->xsec_spline_pointers[iSpline]);
709  }
710  return spline_weight;
711 }
712 
713 // ***************************************************************************
714 // Calculate the normalisation weight for an event
716 // ***************************************************************************
717  M3::float_t xsecw = 1.0;
718  const int nNorms = static_cast<int>(MCEvent->xsec_norm_pointers.size());
719  //Loop over stored normalisation and function pointers
720  #ifdef MULTITHREAD
721  #pragma omp simd
722  #endif
723  for (int iParam = 0; iParam < nNorms; ++iParam)
724  {
725 #pragma GCC diagnostic push
726 #pragma GCC diagnostic ignored "-Wuseless-cast"
727  xsecw *= static_cast<M3::float_t>(*(MCEvent->xsec_norm_pointers[iParam]));
728 #pragma GCC diagnostic pop
729  #ifdef DEBUG
730  if (std::isnan(xsecw)) MACH3LOG_WARN("iParam= {} xsecweight=nan from norms", iParam);
731  #endif
732  }
733  return xsecw;
734 }
735 
736 // ***************************************************************************
737 // Setup the norm parameters
739 // ***************************************************************************
740  std::vector< std::vector< int > > xsec_norms_bins(GetNEvents());
741 
742  std::vector<NormParameter> norm_parameters = ParHandler->GetNormParsFromSampleName(GetSampleName());
743 
744  if(!ParHandler){
745  MACH3LOG_ERROR("ParHandler is not setup!");
746  throw MaCh3Exception(__FILE__ , __LINE__ );
747  }
748 
749  // Assign xsec norm bins in MCSamples tree
750  CalcNormsBins(norm_parameters, xsec_norms_bins);
751 
752  //DB Attempt at reducing impact of SystematicHandlerGeneric::calcReweight()
753  int counter;
754  for (unsigned int iEvent = 0; iEvent < GetNEvents(); ++iEvent) {
755  counter = 0;
756 
757  MCSamples[iEvent].xsec_norm_pointers.resize(xsec_norms_bins[iEvent].size());
758  for(auto const & norm_bin: xsec_norms_bins[iEvent]) {
759  MCSamples[iEvent].xsec_norm_pointers[counter] = ParHandler->RetPointer(norm_bin);
760  counter += 1;
761  }
762  }
763 }
764 
765 // ************************************************
766 //A way to check whether a normalisation parameter applies to an event or not
767 void SampleHandlerFD::CalcNormsBins(std::vector<NormParameter>& norm_parameters, std::vector< std::vector< int > >& xsec_norms_bins) {
768 // ************************************************
769  #ifdef DEBUG
770  std::vector<int> VerboseCounter(norm_parameters.size(), 0);
771  #endif
772  for(unsigned int iEvent = 0; iEvent < GetNEvents(); ++iEvent){
773  std::vector< int > NormBins = {};
774  if (ParHandler) {
775  // Skip oscillated NC events
776  // Not strictly needed, but these events don't get included in oscillated predictions, so
777  // no need to waste our time calculating and storing information about xsec parameters
778  // that will never be used.
779  if (MCSamples[iEvent].isNC && (*MCSamples[iEvent].nupdg != *MCSamples[iEvent].nupdgUnosc) ) {
780  MACH3LOG_TRACE("Event {}, missed NC/signal check", iEvent);
781  continue;
782  } //DB Abstract check on MaCh3Modes to determine which apply to neutral current
783  for (std::vector<NormParameter>::iterator it = norm_parameters.begin(); it != norm_parameters.end(); ++it) {
784  //Now check that the target of an interaction matches with the normalisation parameters
785  bool TargetMatch = MatchCondition((*it).targets, *(MCSamples[iEvent].Target));
786  if (!TargetMatch) {
787  MACH3LOG_TRACE("Event {}, missed target check ({}) for dial {}", iEvent, *(MCSamples[iEvent].Target), (*it).name);
788  continue;
789  }
790 
791  //Now check that the neutrino flavour in an interaction matches with the normalisation parameters
792  bool FlavourMatch = MatchCondition((*it).pdgs, *(MCSamples[iEvent].nupdg));
793  if (!FlavourMatch) {
794  MACH3LOG_TRACE("Event {}, missed PDG check ({}) for dial {}", iEvent,(*MCSamples[iEvent].nupdg), (*it).name);
795  continue;
796  }
797 
798  //Now check that the unoscillated neutrino flavour in an interaction matches with the normalisation parameters
799  bool FlavourUnoscMatch = MatchCondition((*it).preoscpdgs, *(MCSamples[iEvent].nupdgUnosc));
800  if (!FlavourUnoscMatch){
801  MACH3LOG_TRACE("Event {}, missed FlavourUnosc check ({}) for dial {}", iEvent,(*MCSamples[iEvent].nupdgUnosc), (*it).name);
802  continue;
803  }
804 
805  //Now check that the mode of an interaction matches with the normalisation parameters
806  bool ModeMatch = MatchCondition((*it).modes, static_cast<int>(std::round(*(MCSamples[iEvent].mode))));
807  if (!ModeMatch) {
808  MACH3LOG_TRACE("Event {}, missed Mode check ({}) for dial {}", iEvent, *(MCSamples[iEvent].mode), (*it).name);
809  continue;
810  }
811 
812  //Now check whether the norm has kinematic bounds
813  //i.e. does it only apply to events in a particular kinematic region?
814  // Now check whether within kinematic bounds
815  bool IsSelected = true;
816  if ((*it).hasKinBounds) {
817  const auto& kinVars = (*it).KinematicVarStr;
818  const auto& selection = (*it).Selection;
819 
820  for (std::size_t iKinPar = 0; iKinPar < kinVars.size(); ++iKinPar) {
821  const double kinVal = ReturnKinematicParameter(kinVars[iKinPar], static_cast<int>(iEvent));
822 
823  bool passedAnyBound = false;
824  const auto& boundsList = selection[iKinPar];
825 
826  for (const auto& bounds : boundsList) {
827  if (kinVal > bounds[0] && kinVal <= bounds[1]) {
828  passedAnyBound = true;
829  break;
830  }
831  }
832 
833  if (!passedAnyBound) {
834  MACH3LOG_TRACE("Event {}, missed kinematic check ({}) for dial {}",
835  iEvent, kinVars[iKinPar], (*it).name);
836  IsSelected = false;
837  break;
838  }
839  }
840  }
841  // Need to then break the event loop
842  if(!IsSelected){
843  MACH3LOG_TRACE("Event {}, missed Kinematic var check for dial {}", iEvent, (*it).name);
844  continue;
845  }
846  // Now set 'index bin' for each normalisation parameter
847  // All normalisations are just 1 bin for 2015, so bin = index (where index is just the bin for that normalisation)
848  int bin = (*it).index;
849 
850  NormBins.push_back(bin);
851  MACH3LOG_TRACE("Event {}, will be affected by dial {}", iEvent, (*it).name);
852  #ifdef DEBUG
853  VerboseCounter[std::distance(norm_parameters.begin(), it)]++;
854  #endif
855  //}
856  } // end iteration over norm_parameters
857  } // end if (ParHandler)
858  xsec_norms_bins[iEvent] = NormBins;
859  }//end loop over events
860  #ifdef DEBUG
861  MACH3LOG_DEBUG("┌──────────────────────────────────────────────────────────┐");
862  for (std::size_t i = 0; i < norm_parameters.size(); ++i) {
863  const auto& norm = norm_parameters[i];
864  double eventRatio = static_cast<double>(VerboseCounter[i]) / static_cast<double>(GetNEvents());
865 
866  MACH3LOG_DEBUG("│ Param {:<15}, affects {:<8} events ({:>6.2f}%) │",
867  ParHandler->GetParFancyName(norm.index), VerboseCounter[i], eventRatio);
868  }
869  MACH3LOG_DEBUG("└──────────────────────────────────────────────────────────┘");
870  #endif
871 }
872 
873 // ************************************************
874 void SampleHandlerFD::SetupReweightArrays(const size_t numberXBins, const size_t numberYBins) {
875 // ************************************************
876  //Set the number of X and Y bins now
877  Binning.nXBins = numberXBins;
878  Binning.nYBins = numberYBins;
879 
880  // Set total number of bins
882  Binning.GlobalOffset = 0;
883  SampleHandlerFD_array = new double[Binning.nBins];
884  SampleHandlerFD_array_w2 = new double[Binning.nBins];
885  SampleHandlerFD_data = new double[Binning.nBins];
886 
887  for (size_t i = 0; i < Binning.nBins; ++i) {
888  SampleHandlerFD_array[i] = 0.0;
889  SampleHandlerFD_array_w2[i] = 0.0;
890  SampleHandlerFD_data[i] = 0.0;
891  }
892 }
893 
894 //ETA
895 //New versions of set binning functions is SampleHandlerBase
896 //so that we can set the values of the bin and lower/upper
897 //edges in the skmc_base. Hopefully we can use this to make
898 //fill1Dhist and fill2Dhist quicker
899 void SampleHandlerFD::Set1DBinning(size_t nbins, double* boundaries)
900 {
901  SampleDetails._hPDF1D->Reset();
902  SampleDetails._hPDF1D->SetBins(static_cast<int>(nbins),boundaries);
903  SampleDetails.dathist->SetBins(static_cast<int>(nbins),boundaries);
904 
905  Binning.YBinEdges = {-1e8, 1e8};
906 
907  SampleDetails._hPDF2D->Reset();
908  SampleDetails._hPDF2D->SetBins(static_cast<int>(nbins),boundaries,1, Binning.YBinEdges.data());
909  SampleDetails.dathist2d->SetBins(static_cast<int>(nbins),boundaries,1, Binning.YBinEdges.data());
910 
911  //Set the number of X and Y bins now
912  SetupReweightArrays(Binning.XBinEdges.size() - 1, Binning.YBinEdges.size() - 1);
913 
915 }
916 
918  for(unsigned int event_i = 0; event_i < GetNEvents(); event_i++){
919  //Set x_var and y_var values based on XVarStr and YVarStr
920  MCSamples[event_i].x_var = GetPointerToKinematicParameter(GetXBinVarName(), event_i);
921  if (std::isnan(*MCSamples[event_i].x_var) || std::isinf(*MCSamples[event_i].x_var)) {
922  MACH3LOG_ERROR("X var for event {} is ill-defined and equal to {}", event_i, *MCSamples[event_i].x_var);
923  throw MaCh3Exception(__FILE__, __LINE__);
924  }
925 
926  //Give y_var M3::_BAD_DOUBLE_ value for the 1D case since this won't be used
927  MCSamples[event_i].y_var = &(M3::_BAD_DOUBLE_);
928  int bin = SampleDetails._hPDF1D->FindBin(*(MCSamples[event_i].x_var));
929 
930  if ((bin-1) >= 0 && (bin-1) < int(Binning.XBinEdges.size()-1)) {
931  MCSamples[event_i].NomXBin = bin-1;
932  } else {
933  MCSamples[event_i].NomXBin = -1;
934  }
935  MCSamples[event_i].NomYBin = 0;
936  }
937 
939 }
940 
941 void SampleHandlerFD::Set2DBinning(size_t nbins1, double* boundaries1, size_t nbins2, double* boundaries2)
942 {
943  SampleDetails._hPDF1D->Reset();
944  SampleDetails._hPDF1D->SetBins(static_cast<int>(nbins1),boundaries1);
945  SampleDetails.dathist->SetBins(static_cast<int>(nbins1),boundaries1);
946 
947  SampleDetails._hPDF2D->Reset();
948  SampleDetails._hPDF2D->SetBins(static_cast<int>(nbins1),boundaries1,static_cast<int>(nbins2),boundaries2);
949  SampleDetails.dathist2d->SetBins(static_cast<int>(nbins1),boundaries1,static_cast<int>(nbins2),boundaries2);
950 
951  //Set the number of X and Y bins now
952  SetupReweightArrays(Binning.XBinEdges.size() - 1, Binning.YBinEdges.size() - 1);
953 
955 }
956 
957 // ************************************************
959 // ************************************************
960  for(unsigned int event_i = 0 ; event_i < GetNEvents(); event_i++) {
961  //Set x_var and y_var values based on XVarStr and YVarStr
962  MCSamples[event_i].x_var = GetPointerToKinematicParameter(GetXBinVarName(), event_i);
963  MCSamples[event_i].y_var = GetPointerToKinematicParameter(GetYBinVarName(), event_i);
964 
965  if (std::isnan(*MCSamples[event_i].x_var) || std::isinf(*MCSamples[event_i].x_var)) {
966  MACH3LOG_ERROR("X var for event {} is ill-defined and equal to {}", event_i, *MCSamples[event_i].x_var);
967  throw MaCh3Exception(__FILE__, __LINE__);
968  }
969  if (std::isnan(*MCSamples[event_i].y_var) || std::isinf(*MCSamples[event_i].y_var)) {
970  MACH3LOG_ERROR("Y var for event {} is ill-defined and equal to {}", event_i, *MCSamples[event_i].y_var);
971  throw MaCh3Exception(__FILE__, __LINE__);
972  }
973  //Global bin number
974  int bin = SampleDetails._hPDF2D->FindBin(*(MCSamples[event_i].x_var), *(MCSamples[event_i].y_var));
975 
976  int bin_x = M3::_BAD_INT_;
977  int bin_y = M3::_BAD_INT_;
978  int bin_z = M3::_BAD_INT_;
979  SampleDetails._hPDF2D->GetBinXYZ(bin, bin_x, bin_y, bin_z);
980 
981  if ((bin_x-1) >= 0 && (bin_x-1) < int(Binning.XBinEdges.size()-1)) {
982  MCSamples[event_i].NomXBin = bin_x-1;
983  }
984  MCSamples[event_i].NomYBin = bin_y-1;
985  if(MCSamples[event_i].NomYBin < 0){
986  MACH3LOG_WARN("Nominal YBin PROBLEM, y-bin is {}", MCSamples[event_i].NomYBin);
987  }
988  }
990 }
991 
992 #pragma GCC diagnostic push
993 #pragma GCC diagnostic ignored "-Wconversion"
994 
995 // ************************************************
996 TH1* SampleHandlerFD::GetW2Hist(const int Dimension) {
997 // ************************************************
998  if(Dimension == 1) {
999  TH1D* W2Hist = dynamic_cast<TH1D*>(SampleDetails._hPDF1D->Clone((SampleDetails._hPDF1D->GetName() + std::string("_W2")).c_str()));
1000  if (!W2Hist) {
1001  MACH3LOG_ERROR("Failed to cast");
1002  throw MaCh3Exception(__FILE__, __LINE__);
1003  }
1004  W2Hist->Reset();
1005  for (size_t yBin = 0; yBin < Binning.nYBins; ++yBin) {
1006  for (size_t xBin = 0; xBin < Binning.nXBins; ++xBin) {
1007  const int idx = Binning.GetBinSafe(xBin, yBin);
1008  W2Hist->AddBinContent(idx + 1, SampleHandlerFD_array_w2[idx]);
1009  }
1010  }
1011  return W2Hist;
1012  } else if(Dimension == 2) {
1013  TH2D* W2Hist = dynamic_cast<TH2D*>(SampleDetails._hPDF2D->Clone((SampleDetails._hPDF2D->GetName() + std::string("_W2")).c_str()));
1014  if (!W2Hist) {
1015  MACH3LOG_ERROR("Failed to cast");
1016  throw MaCh3Exception(__FILE__, __LINE__);
1017  }
1018  W2Hist->Reset();
1019  for (size_t yBin = 0; yBin < Binning.nYBins; ++yBin) {
1020  for (size_t xBin = 0; xBin < Binning.nXBins; ++xBin) {
1021  const int idx = Binning.GetBinSafe(xBin, yBin);
1022  W2Hist->SetBinContent(static_cast<int>(xBin + 1), static_cast<int>(yBin + 1), SampleHandlerFD_array_w2[idx]);
1023  }
1024  }
1025  return W2Hist;
1026  } else{
1027  MACH3LOG_ERROR("Asking for {} with N Dimension = {}. This is not implemented", __func__, Dimension);
1028  throw MaCh3Exception(__FILE__, __LINE__);
1029  }
1030 }
1031 #pragma GCC diagnostic pop
1032 
1033 // ************************************************
1034 TH1* SampleHandlerFD::GetMCHist(const int Dimension) {
1035 // ************************************************
1036  FillMCHist(Dimension);
1037 
1038  if(Dimension == 1) {
1039  return SampleDetails._hPDF1D;
1040  } else if(Dimension == 2) {
1041  return SampleDetails._hPDF2D;
1042  } else{
1043  MACH3LOG_ERROR("Asking for {} with N Dimension = {}. This is not implemented", __func__, Dimension);
1044  throw MaCh3Exception(__FILE__, __LINE__);
1045  }
1046 }
1047 
1048 // ************************************************
1049 TH1* SampleHandlerFD::GetDataHist(const int Dimension) {
1050 // ************************************************
1051  if(Dimension == 1) {
1052  return SampleDetails.dathist;
1053  } else if(Dimension == 2) {
1054  return SampleDetails.dathist2d;
1055  } else{
1056  MACH3LOG_ERROR("Asdking for {} with N Dimension = {}. This is not implemented", __func__, Dimension);
1057  throw MaCh3Exception(__FILE__, __LINE__);
1058  }
1059 }
1060 
1061 void SampleHandlerFD::AddData(std::vector<double> &data) {
1062  if (SampleDetails.dathist == nullptr) {
1063  MACH3LOG_ERROR("Data hist hasn't been initialised yet");
1064  throw MaCh3Exception(__FILE__, __LINE__);
1065  }
1066 
1067  SampleDetails.dathist2d = nullptr;
1068  SampleDetails.dathist->Reset();
1069 
1070  if (GetNDim()!=1) {
1071  MACH3LOG_ERROR("Trying to set a 1D 'data' histogram when the number of dimensions for this sample is {}", GetNDim());
1072  MACH3LOG_ERROR("This won't work, please specify the correct dimensions in your sample config with the X and Y variables");
1073  throw MaCh3Exception(__FILE__, __LINE__);
1074  }
1075 
1076  for (auto const& data_point : data){
1077  SampleDetails.dathist->Fill(data_point);
1078  }
1079 
1080  if(SampleHandlerFD_data == nullptr) {
1081  MACH3LOG_ERROR("SampleHandlerFD_data haven't been initialised yet");
1082  throw MaCh3Exception(__FILE__, __LINE__);
1083  }
1084  // Assuming nBins == nXBins here, because you have 1D data
1085  for (size_t bin = 0; bin < Binning.nXBins; ++bin) {
1086  // ROOT histograms are 1-based, so bin index + 1
1087  SampleHandlerFD_data[bin] = SampleDetails.dathist->GetBinContent(static_cast<int>(bin + 1));
1088  }
1089 }
1090 
1091 #pragma GCC diagnostic push
1092 #pragma GCC diagnostic ignored "-Wconversion"
1093 void SampleHandlerFD::AddData(std::vector< std::vector <double> > &data) {
1094  if (SampleDetails.dathist2d == nullptr) {
1095  MACH3LOG_ERROR("Data hist hasn't been initialised yet");
1096  throw MaCh3Exception(__FILE__, __LINE__);
1097  }
1098 
1099  SampleDetails.dathist = nullptr;
1100  SampleDetails.dathist2d->Reset();
1101 
1102  if (GetNDim()!=2) {
1103  MACH3LOG_ERROR("Trying to set a 2D 'data' histogram when the number of dimensions for this sample is {}", GetNDim());
1104  MACH3LOG_ERROR("This won't work, please specify the correct dimensions in your sample config with the X and Y variables");
1105  throw MaCh3Exception(__FILE__, __LINE__);
1106  }
1107 
1108  //TODO: this assumes that std::vector is of length 2 and then both data.at(0) and data
1109  //ETA: I think this might just be wrong? We should probably just make this AddData(std::vector<double> data_x, std::vector<double> data_y)
1110  // or maybe something like AddData(std::vector<std::pair<double, double>> data)?
1111  for (int i = 0; i < int(data.size()); i++) {
1112  SampleDetails.dathist2d->Fill(data.at(0)[i],data.at(1)[i]);
1113  }
1114 
1115  if(SampleHandlerFD_data == nullptr) {
1116  MACH3LOG_ERROR("SampleHandlerFD_data haven't been initialised yet");
1117  throw MaCh3Exception(__FILE__, __LINE__);
1118  }
1119  for (size_t yBin=0;yBin<Binning.nYBins;yBin++) {
1120  for (size_t xBin=0;xBin<Binning.nXBins;xBin++) {
1121  //Need to cast to an int (Int_t) for ROOT
1122  //Need to do +1 for the bin, this is to be consistent with ROOTs binning scheme
1123  const int idx = Binning.GetBinSafe(xBin, yBin);
1124  SampleHandlerFD_data[idx] = SampleDetails.dathist2d->GetBinContent(static_cast<int>(xBin + 1), static_cast<int>(yBin + 1));
1125  }
1126  }
1127 }
1128 
1129 void SampleHandlerFD::AddData(TH1D* Data) {
1130  MACH3LOG_INFO("Adding 1D data histogram: {} with {:.2f} events", Data->GetTitle(), Data->Integral());
1131  if (SampleDetails.dathist != nullptr) {
1132  delete SampleDetails.dathist;
1133  }
1134  SampleDetails.dathist2d = nullptr;
1135  SampleDetails.dathist = static_cast<TH1D*>(Data->Clone());
1136 
1137  if (GetNDim() != 1) {
1138  MACH3LOG_ERROR("Trying to set a 1D 'data' histogram in a 2D sample - Quitting");
1139  MACH3LOG_ERROR("The number of dimensions for this sample is {}", GetNDim());
1140  throw MaCh3Exception(__FILE__ , __LINE__ );
1141  }
1142 
1143  if(SampleHandlerFD_data == nullptr) {
1144  MACH3LOG_ERROR("SampleHandlerFD_data haven't been initialised yet");
1145  throw MaCh3Exception(__FILE__, __LINE__);
1146  }
1147 
1148  for (size_t bin = 0; bin < Binning.nXBins; ++bin) {
1149  // ROOT histograms are 1-based, so bin index + 1
1150  SampleHandlerFD_data[bin] = SampleDetails.dathist->GetBinContent(static_cast<int>(bin + 1));
1151  }
1152 }
1153 
1154 void SampleHandlerFD::AddData(TH2D* Data) {
1155  MACH3LOG_INFO("Adding 2D data histogram: {} with {:.2f} events", Data->GetTitle(), Data->Integral());
1156  if (SampleDetails.dathist2d != nullptr) {
1157  delete SampleDetails.dathist2d;
1158  }
1159  SampleDetails.dathist2d = static_cast<TH2D*>(Data->Clone());
1160  SampleDetails.dathist = nullptr;
1161 
1162  if (GetNDim() != 2) {
1163  MACH3LOG_ERROR("Trying to set a 2D 'data' histogram in a 1D sample - Quitting");
1164  throw MaCh3Exception(__FILE__ , __LINE__ );}
1165 
1166  if(SampleHandlerFD_data == nullptr) {
1167  MACH3LOG_ERROR("SampleHandlerFD_data haven't been initialised yet");
1168  throw MaCh3Exception(__FILE__, __LINE__);
1169  }
1170  for (size_t yBin=0;yBin<Binning.nYBins;yBin++) {
1171  for (size_t xBin=0;xBin<Binning.nXBins;xBin++) {
1172  //Need to cast to an int (Int_t) for ROOT
1173  //Need to do +1 for the bin, this is to be consistent with ROOTs binning scheme
1174  const int idx = Binning.GetBinSafe(xBin, yBin);
1175  SampleHandlerFD_data[idx] = SampleDetails.dathist2d->GetBinContent(static_cast<int>(xBin + 1), static_cast<int>(yBin + 1));
1176  }
1177  }
1178 }
1179 #pragma GCC diagnostic pop
1180 
1181 // ************************************************
1183 // ************************************************
1184  auto NuOscillatorConfigFile = Get<std::string>(SampleManager->raw()["NuOsc"]["NuOscConfigFile"], __FILE__ , __LINE__);
1185  auto EqualBinningPerOscChannel = Get<bool>(SampleManager->raw()["NuOsc"]["EqualBinningPerOscChannel"], __FILE__ , __LINE__);
1186 
1187  // TN override the sample setting if not using binned oscillation
1188  if (EqualBinningPerOscChannel) {
1189  if (YAML::LoadFile(NuOscillatorConfigFile)["General"]["CalculationType"].as<std::string>() == "Unbinned") {
1190  MACH3LOG_WARN("Tried using EqualBinningPerOscChannel while using Unbinned oscillation calculation, changing EqualBinningPerOscChannel to false");
1191  EqualBinningPerOscChannel = false;
1192  }
1193  }
1194  std::vector<const double*> OscParams = ParHandler->GetOscParsFromSampleName(SampleName);
1195  if (OscParams.empty()) {
1196  MACH3LOG_ERROR("OscParams is empty for sample '{}'.", GetTitle());
1197  MACH3LOG_ERROR("This likely indicates an error in your oscillation YAML configuration.");
1198  throw MaCh3Exception(__FILE__, __LINE__);
1199  }
1200  Oscillator = std::make_shared<OscillationHandler>(NuOscillatorConfigFile, EqualBinningPerOscChannel, OscParams, GetNOscChannels());
1201 
1202  if (!EqualBinningPerOscChannel) {
1203  for(int iChannel = 0; iChannel < GetNOscChannels(); iChannel++) {
1204  std::vector<M3::float_t> EnergyArray;
1205  std::vector<M3::float_t> CosineZArray;
1206 
1207  for (unsigned int iEvent = 0; iEvent < GetNEvents(); iEvent++) {
1208  // KS: This is bit weird but we basically loop over all events and push to vector only these which are part of a given OscChannel
1209  const int Channel = GetOscChannel(OscChannels, (*MCSamples[iEvent].nupdgUnosc), (*MCSamples[iEvent].nupdg));
1210  //DB Remove NC events from the arrays which are handed to the NuOscillator objects
1211  if (!MCSamples[iEvent].isNC && Channel == iChannel) {
1212  EnergyArray.push_back(M3::float_t(*(MCSamples[iEvent].rw_etru)));
1213  }
1214  }
1215  std::sort(EnergyArray.begin(),EnergyArray.end());
1216 
1217  //============================================================================
1218  //DB Atmospheric only part, can only happen if truecz has been initialised within the experiment specific code
1219  if (*(MCSamples[0].rw_truecz) != M3::_BAD_DOUBLE_) {
1220  for (unsigned int iEvent = 0; iEvent < GetNEvents(); iEvent++) {
1221  // KS: This is bit weird but we basically loop over all events and push to vector only these which are part of a given OscChannel
1222  const int Channel = GetOscChannel(OscChannels, (*MCSamples[iEvent].nupdgUnosc), (*MCSamples[iEvent].nupdg));
1223  //DB Remove NC events from the arrays which are handed to the NuOscillator objects
1224  if (!MCSamples[iEvent].isNC && Channel == iChannel) {
1225  CosineZArray.push_back(M3::float_t(*(MCSamples[iEvent].rw_truecz)));
1226  }
1227  }
1228  std::sort(CosineZArray.begin(),CosineZArray.end());
1229  }
1230  Oscillator->SetOscillatorBinning(iChannel, EnergyArray, CosineZArray);
1231  } // end loop over channels
1232  }
1233 }
1234 
1236  for (unsigned int iEvent=0;iEvent<GetNEvents();iEvent++) {
1237  MCSamples[iEvent].osc_w_pointer = &M3::Unity;
1238  if (MCSamples[iEvent].isNC) {
1239  if (*MCSamples[iEvent].nupdg != *MCSamples[iEvent].nupdgUnosc) {
1240  MCSamples[iEvent].osc_w_pointer = &M3::Zero;
1241  } else {
1242  MCSamples[iEvent].osc_w_pointer = &M3::Unity;
1243  }
1244  } else {
1245  int InitFlav = M3::_BAD_INT_;
1246  int FinalFlav = M3::_BAD_INT_;
1247 
1248  InitFlav = MaCh3Utils::PDGToNuOscillatorFlavour((*MCSamples[iEvent].nupdgUnosc));
1249  FinalFlav = MaCh3Utils::PDGToNuOscillatorFlavour((*MCSamples[iEvent].nupdg));
1250 
1251  if (InitFlav == M3::_BAD_INT_ || FinalFlav == M3::_BAD_INT_) {
1252  MACH3LOG_ERROR("Something has gone wrong in the mapping between MCSamples.nutype and the enum used within NuOscillator");
1253  MACH3LOG_ERROR("MCSamples.nupdgUnosc: {}", (*MCSamples[iEvent].nupdgUnosc));
1254  MACH3LOG_ERROR("InitFlav: {}", InitFlav);
1255  MACH3LOG_ERROR("MCSamples.nupdg: {}", (*MCSamples[iEvent].nupdg));
1256  MACH3LOG_ERROR("FinalFlav: {}", FinalFlav);
1257  throw MaCh3Exception(__FILE__, __LINE__);
1258  }
1259 
1260  const int OscIndex = GetOscChannel(OscChannels, (*MCSamples[iEvent].nupdgUnosc), (*MCSamples[iEvent].nupdg));
1261  //Can only happen if truecz has been initialised within the experiment specific code
1262  if (*(MCSamples[iEvent].rw_truecz) != M3::_BAD_DOUBLE_) {
1263  //Atmospherics
1264  MCSamples[iEvent].osc_w_pointer = Oscillator->GetNuOscillatorPointers(OscIndex, InitFlav, FinalFlav, FLOAT_T(*(MCSamples[iEvent].rw_etru)), FLOAT_T(*(MCSamples[iEvent].rw_truecz)));
1265  } else {
1266  //Beam
1267  MCSamples[iEvent].osc_w_pointer = Oscillator->GetNuOscillatorPointers(OscIndex, InitFlav, FinalFlav, FLOAT_T(*(MCSamples[iEvent].rw_etru)));
1268  }
1269  } // end if NC
1270  } // end loop over events
1271 }
1272 
1273 std::string SampleHandlerFD::GetSampleName(int iSample) const {
1274  //ETA - this is just to suppress a warning for an unused variable
1275  (void)iSample;
1276 
1277  //ETA - extra safety to make sure SampleName is actually set
1278  // probably unnecessary due to the requirement for it to be in the yaml config
1279  if(SampleName.length() == 0){
1280  MACH3LOG_ERROR("No sample name provided");
1281  MACH3LOG_ERROR("Please provide a SampleName in your configuration file: {}", SampleManager->GetFileName());
1282  throw MaCh3Exception(__FILE__, __LINE__);
1283  }
1284  return SampleName;
1285 }
1286 
1288  M3::float_t totalweight = 1.0;
1289  const int nParams = static_cast<int>(MCSamples[iEntry].total_weight_pointers.size());
1290  #ifdef MULTITHREAD
1291  #pragma omp simd
1292  #endif
1293  for (int iParam = 0; iParam < nParams; ++iParam) {
1294  totalweight *= *(MCSamples[iEntry].total_weight_pointers[iParam]);
1295  }
1296 
1297  return totalweight;
1298 }
1299 
1300 // Finds the binned spline that an event should apply to and stored them in a
1301 // a vector for easy evaluation in the fillArray() function.
1303  //Now loop over events and get the spline bin for each event
1304  for (unsigned int j = 0; j < GetNEvents(); ++j) {
1305  const int OscIndex = GetOscChannel(OscChannels, (*MCSamples[j].nupdgUnosc), (*MCSamples[j].nupdg));
1306 
1307  std::vector< std::vector<int> > EventSplines;
1308  switch(GetNDim()){
1309  case 1:
1310  EventSplines = SplineHandler->GetEventSplines(GetTitle(), OscIndex, int(*(MCSamples[j].mode)), *(MCSamples[j].rw_etru), *(MCSamples[j].x_var), 0.);
1311  break;
1312  case 2:
1313  EventSplines = SplineHandler->GetEventSplines(GetTitle(), OscIndex, int(*(MCSamples[j].mode)), *(MCSamples[j].rw_etru), *(MCSamples[j].x_var), *(MCSamples[j].y_var));
1314  break;
1315  default:
1316  MACH3LOG_ERROR("Error in assigning spline bins because nDimensions = {}", GetNDim());
1317  MACH3LOG_ERROR("MaCh3 only supports splines binned in Etrue + the sample binning");
1318  MACH3LOG_ERROR("Please check the sample binning you specified in your sample config ");
1319  throw MaCh3Exception(__FILE__, __LINE__);
1320  break;
1321  }
1322  int NSplines = int(EventSplines.size());
1323  if(NSplines < 0){
1324  throw MaCh3Exception(__FILE__, __LINE__);
1325  }
1326  MCSamples[j].xsec_spline_pointers.resize(NSplines);
1327  for(size_t spline = 0; spline < MCSamples[j].xsec_spline_pointers.size(); spline++) {
1328  //Event Splines indexed as: sample name, oscillation channel, syst, mode, etrue, var1, var2 (var2 is a dummy 0 for 1D splines)
1329  MCSamples[j].xsec_spline_pointers[spline] = SplineHandler->retPointer(EventSplines[spline][0], EventSplines[spline][1],
1330  EventSplines[spline][2], EventSplines[spline][3],
1331  EventSplines[spline][4], EventSplines[spline][5],
1332  EventSplines[spline][6]);
1333  }
1334  }
1335 }
1336 
1337 // ************************************************
1339 // ************************************************
1340  if (SampleHandlerFD_data == nullptr) {
1341  MACH3LOG_ERROR("Data sample is empty! Can't calculate a likelihood!");
1342  throw MaCh3Exception(__FILE__, __LINE__);
1343  }
1344 
1345  double negLogL = 0.;
1346  #ifdef MULTITHREAD
1347  #pragma omp parallel for reduction(+:negLogL)
1348  #endif
1349  for (size_t idx = 0; idx < Binning.nBins; ++idx)
1350  {
1351  const double DataVal = SampleHandlerFD_data[idx];
1352  const double MCPred = SampleHandlerFD_array[idx];
1353  const double w2 = SampleHandlerFD_array_w2[idx];
1354 
1355  //KS: Calculate likelihood using Barlow-Beeston Poisson or even IceCube
1356  negLogL += GetTestStatLLH(DataVal, MCPred, w2);
1357  }
1358  return negLogL;
1359 }
1360 
1362  MCSamples.resize(nEvents);
1363 }
1364 
1365 
1366 // ************************************************
1368 // ************************************************
1369  Dir->cd();
1370 
1371  YAML::Node Config = SampleManager->raw();
1372  TMacro ConfigSave = YAMLtoTMacro(Config, (std::string("Config_") + GetTitle()));
1373  ConfigSave.Write();
1374 
1375  std::unique_ptr<TH1> data_hist;
1376 
1377  if (GetNDim() == 1) {
1378  data_hist = M3::Clone<TH1D>(dynamic_cast<TH1D*>(GetDataHist(1)), "data_" + GetTitle());
1379  data_hist->GetXaxis()->SetTitle(GetXBinVarName().c_str());
1380  data_hist->GetYaxis()->SetTitle("Number of Events");
1381  } else if (GetNDim() == 2) {
1382  data_hist = M3::Clone<TH2D>(dynamic_cast<TH2D*>(GetDataHist(2)), "data_" + GetTitle());
1383  data_hist->GetXaxis()->SetTitle(GetXBinVarName().c_str());
1384  data_hist->GetYaxis()->SetTitle(GetYBinVarName().c_str());
1385  data_hist->GetZaxis()->SetTitle("Number of Events");
1386  } else {
1387  MACH3LOG_ERROR("Not implemented");
1388  throw MaCh3Exception(__FILE__, __LINE__);
1389  }
1390 
1391  if (!data_hist) {
1392  MACH3LOG_ERROR("nullptr data hist :(");
1393  throw MaCh3Exception(__FILE__, __LINE__);
1394  }
1395 
1396  data_hist->SetTitle(("data_" + GetTitle()).c_str());
1397  data_hist->Write();
1398 }
1399 
1401  std::vector<std::string> spline_filepaths;
1402  for(int iChannel = 0 ; iChannel < GetNOscChannels(); iChannel++){
1403  spline_filepaths.push_back(spline_files[iChannel]);
1404  }
1405 
1406  //Keep a track of the spline variables
1407  std::vector<std::string> SplineVarNames = {"TrueNeutrinoEnergy"};
1408  if(GetXBinVarName().length() > 0){
1409  SplineVarNames.push_back(GetXBinVarName());
1410  }
1411  if(GetYBinVarName().length() > 0){
1412  SplineVarNames.push_back(GetYBinVarName());
1413  }
1414 
1415  bool LoadSplineFile = GetFromManager<bool>(SampleManager->raw()["InputFiles"]["LoadSplineFile"], false, __FILE__, __LINE__);
1416  bool PrepSplineFile = GetFromManager<bool>(SampleManager->raw()["InputFiles"]["PrepSplineFile"], false, __FILE__, __LINE__);
1417  auto SplineFileName = GetFromManager<std::string>(SampleManager->raw()["InputFiles"]["SplineFileName"],
1418  (SampleName + "_SplineFile.root"), __FILE__, __LINE__);
1419  if(!LoadSplineFile) {
1420  SplineHandler->AddSample(SampleName, GetTitle(), spline_filepaths, SplineVarNames);
1421  SplineHandler->CountNumberOfLoadedSplines(false, 1);
1422  SplineHandler->TransferToMonolith();
1423  if(PrepSplineFile) SplineHandler->PrepareSplineFile(SplineFileName);
1424  } else {
1425  // KS: Skip default spline loading and use flattened spline format allowing to read stuff much faster
1426  SplineHandler->LoadSplineFile(SplineFileName);
1427  }
1428  MACH3LOG_INFO("--------------------------------");
1429  MACH3LOG_INFO("Setup Far Detector splines");
1430 
1431  FillSplineBins();
1432 
1433  SplineHandler->cleanUpMemory();
1434 }
1435 
1436 // === JM adjust GetNDVarHist functions to allow for subevent-level plotting ===
1437 TH1* SampleHandlerFD::Get1DVarHist(const std::string& ProjectionVar_Str, const std::vector< KinematicCut >& EventSelectionVec,
1438  int WeightStyle, TAxis* Axis, const std::vector< KinematicCut >& SubEventSelectionVec) {
1439  //DB Need to overwrite the Selection member variable so that IsEventSelected function operates correctly.
1440  // Consequently, store the selection cuts already saved in the sample, overwrite the Selection variable, then reset
1441  std::vector< KinematicCut > tmp_Selection = Selection;
1442  std::vector< KinematicCut > SelectionVecToApply;
1443 
1444  //DB Add all the predefined selections to the selection vector which will be applied
1445  for (size_t iSelec=0;iSelec<Selection.size();iSelec++) {
1446  SelectionVecToApply.emplace_back(Selection[iSelec]);
1447  }
1448 
1449  //DB Add all requested cuts from the argument to the selection vector which will be applied
1450  for (size_t iSelec=0;iSelec<EventSelectionVec.size();iSelec++) {
1451  SelectionVecToApply.emplace_back(EventSelectionVec[iSelec]);
1452  }
1453 
1454  //DB Set the member variable to be the cuts to apply
1455  Selection = SelectionVecToApply;
1456 
1457  //DB Define the histogram which will be returned
1458  TH1D* _h1DVar;
1459  if (Axis) {
1460  _h1DVar = new TH1D("","",Axis->GetNbins(),Axis->GetXbins()->GetArray());
1461  } else {
1462  std::vector<double> xBinEdges = ReturnKinematicParameterBinning(ProjectionVar_Str);
1463  _h1DVar = new TH1D("", "", int(xBinEdges.size())-1, xBinEdges.data());
1464  }
1465  _h1DVar->GetXaxis()->SetTitle(ProjectionVar_Str.c_str());
1466 
1467  if (IsSubEventVarString(ProjectionVar_Str)) {
1468  Fill1DSubEventHist(_h1DVar, ProjectionVar_Str, SubEventSelectionVec, WeightStyle);
1469  } else {
1470  //DB Grab the associated enum with the argument string
1471  int ProjectionVar_Int = ReturnKinematicParameterFromString(ProjectionVar_Str);
1472 
1473  //DB Loop over all events
1474  for (unsigned int iEvent = 0; iEvent < GetNEvents(); iEvent++) {
1475  if (IsEventSelected(iEvent)) {
1476  double Weight = GetEventWeight(iEvent);
1477  if (WeightStyle == 1) {
1478  Weight = 1.;
1479  }
1480  double Var = ReturnKinematicParameter(ProjectionVar_Int,iEvent);
1481  _h1DVar->Fill(Var,Weight);
1482  }
1483  }
1484  }
1485  //DB Reset the saved selection
1486  Selection = tmp_Selection;
1487 
1488  return _h1DVar;
1489 }
1490 
1491 void SampleHandlerFD::Fill1DSubEventHist(TH1D* _h1DVar, const std::string& ProjectionVar_Str, const std::vector< KinematicCut >& SubEventSelectionVec, int WeightStyle) {
1492  int ProjectionVar_Int = ReturnKinematicVectorFromString(ProjectionVar_Str);
1493 
1494  //JM Loop over all events
1495  for (unsigned int iEvent = 0; iEvent < GetNEvents(); iEvent++) {
1496  if (IsEventSelected(iEvent)) {
1497  double Weight = GetEventWeight(iEvent);
1498  if (WeightStyle == 1) {
1499  Weight = 1.;
1500  }
1501  std::vector<double> Vec = ReturnKinematicVector(ProjectionVar_Int,iEvent);
1502  size_t nsubevents = Vec.size();
1503  //JM Loop over all subevents in event
1504  for (unsigned int iSubEvent = 0; iSubEvent < nsubevents; iSubEvent++) {
1505  if (IsSubEventSelected(SubEventSelectionVec, iEvent, iSubEvent, nsubevents)) {
1506  double Var = Vec[iSubEvent];
1507  _h1DVar->Fill(Var,Weight);
1508  }
1509  }
1510  }
1511  }
1512 }
1513 
1514 // ************************************************
1515 TH2* SampleHandlerFD::Get2DVarHist(const std::string& ProjectionVar_StrX, const std::string& ProjectionVar_StrY,
1516  const std::vector< KinematicCut >& EventSelectionVec, int WeightStyle, TAxis* AxisX, TAxis* AxisY, const std::vector< KinematicCut >& SubEventSelectionVec) {
1517 // ************************************************
1518  //DB Need to overwrite the Selection member variable so that IsEventSelected function operates correctly.
1519  // Consequently, store the selection cuts already saved in the sample, overwrite the Selection variable, then reset
1520  std::vector< KinematicCut > tmp_Selection = Selection;
1521  std::vector< KinematicCut > SelectionVecToApply;
1522 
1523  //DB Add all the predefined selections to the selection vector which will be applied
1524  for (size_t iSelec=0;iSelec<Selection.size();iSelec++) {
1525  SelectionVecToApply.emplace_back(Selection[iSelec]);
1526  }
1527 
1528  //DB Add all requested cuts from the argument to the selection vector which will be applied
1529  for (size_t iSelec=0;iSelec<EventSelectionVec.size();iSelec++) {
1530  SelectionVecToApply.emplace_back(EventSelectionVec[iSelec]);
1531  }
1532 
1533  //DB Set the member variable to be the cuts to apply
1534  Selection = SelectionVecToApply;
1535 
1536  //DB Define the histogram which will be returned
1537  TH2D* _h2DVar;
1538  if (AxisX && AxisY) {
1539  _h2DVar = new TH2D("","",AxisX->GetNbins(),AxisX->GetXbins()->GetArray(),AxisY->GetNbins(),AxisY->GetXbins()->GetArray());
1540  } else {
1541  std::vector<double> xBinEdges = ReturnKinematicParameterBinning(ProjectionVar_StrX);
1542  std::vector<double> yBinEdges = ReturnKinematicParameterBinning(ProjectionVar_StrY);
1543  _h2DVar = new TH2D("", "", int(xBinEdges.size())-1, xBinEdges.data(), int(yBinEdges.size())-1, yBinEdges.data());
1544  }
1545 
1546  bool IsSubEventHist = IsSubEventVarString(ProjectionVar_StrX) || IsSubEventVarString(ProjectionVar_StrY);
1547  if (IsSubEventHist) Fill2DSubEventHist(_h2DVar, ProjectionVar_StrX, ProjectionVar_StrY, SubEventSelectionVec, WeightStyle);
1548  else {
1549  //DB Grab the associated enum with the argument string
1550  int ProjectionVar_IntX = ReturnKinematicParameterFromString(ProjectionVar_StrX);
1551  int ProjectionVar_IntY = ReturnKinematicParameterFromString(ProjectionVar_StrY);
1552 
1553  //DB Loop over all events
1554  for (unsigned int iEvent = 0; iEvent < GetNEvents(); iEvent++) {
1555  if (IsEventSelected(iEvent)) {
1556  double Weight = GetEventWeight(iEvent);
1557  if (WeightStyle == 1) {
1558  Weight = 1.;
1559  }
1560  double VarX = ReturnKinematicParameter(ProjectionVar_IntX, iEvent);
1561  double VarY = ReturnKinematicParameter(ProjectionVar_IntY, iEvent);
1562  _h2DVar->Fill(VarX,VarY,Weight);
1563  }
1564  }
1565  }
1566  //DB Reset the saved selection
1567  Selection = tmp_Selection;
1568 
1569  return _h2DVar;
1570 }
1571 
1572 void SampleHandlerFD::Fill2DSubEventHist(TH2D* _h2DVar, const std::string& ProjectionVar_StrX, const std::string& ProjectionVar_StrY,
1573  const std::vector< KinematicCut >& SubEventSelectionVec, int WeightStyle) {
1574  bool IsSubEventVarX = IsSubEventVarString(ProjectionVar_StrX);
1575  bool IsSubEventVarY = IsSubEventVarString(ProjectionVar_StrY);
1576 
1577  int ProjectionVar_IntX, ProjectionVar_IntY;
1578  if (IsSubEventVarX) ProjectionVar_IntX = ReturnKinematicVectorFromString(ProjectionVar_StrX);
1579  else ProjectionVar_IntX = ReturnKinematicParameterFromString(ProjectionVar_StrX);
1580  if (IsSubEventVarY) ProjectionVar_IntY = ReturnKinematicVectorFromString(ProjectionVar_StrY);
1581  else ProjectionVar_IntY = ReturnKinematicParameterFromString(ProjectionVar_StrY);
1582 
1583  //JM Loop over all events
1584  for (unsigned int iEvent = 0; iEvent < GetNEvents(); iEvent++) {
1585  if (IsEventSelected(iEvent)) {
1586  double Weight = GetEventWeight(iEvent);
1587  if (WeightStyle == 1) {
1588  Weight = 1.;
1589  }
1590  std::vector<double> VecX = {}, VecY = {};
1591  double VarX = M3::_BAD_DOUBLE_, VarY = M3::_BAD_DOUBLE_;
1592  size_t nsubevents = 0;
1593  // JM Three cases: subeventX vs eventY || eventX vs subeventY || subeventX vs subeventY
1594  if (IsSubEventVarX && !IsSubEventVarY) {
1595  VecX = ReturnKinematicVector(ProjectionVar_IntX, iEvent);
1596  VarY = ReturnKinematicParameter(ProjectionVar_IntY, iEvent);
1597  nsubevents = VecX.size();
1598  }
1599  else if (!IsSubEventVarX && IsSubEventVarY) {
1600  VecY = ReturnKinematicVector(ProjectionVar_IntY, iEvent);
1601  VarX = ReturnKinematicParameter(ProjectionVar_IntX, iEvent);
1602  nsubevents = VecY.size();
1603  }
1604  else {
1605  VecX = ReturnKinematicVector(ProjectionVar_IntX, iEvent);
1606  VecY = ReturnKinematicVector(ProjectionVar_IntY, iEvent);
1607  if (VecX.size() != VecY.size()) {
1608  MACH3LOG_ERROR("Cannot plot {} of size {} against {} of size {}", ProjectionVar_StrX, VecX.size(), ProjectionVar_StrY, VecY.size());
1609  throw MaCh3Exception(__FILE__, __LINE__);
1610  }
1611  nsubevents = VecX.size();
1612  }
1613  //JM Loop over all subevents in event
1614  for (unsigned int iSubEvent = 0; iSubEvent < nsubevents; iSubEvent++) {
1615  if (IsSubEventSelected(SubEventSelectionVec, iEvent, iSubEvent, nsubevents)) {
1616  if (IsSubEventVarX) VarX = VecX[iSubEvent];
1617  if (IsSubEventVarY) VarY = VecY[iSubEvent];
1618  _h2DVar->Fill(VarX,VarY,Weight);
1619  }
1620  }
1621  }
1622  }
1623 }
1624 // ================================================
1625 
1626 // ************************************************
1627 int SampleHandlerFD::ReturnKinematicParameterFromString(const std::string& KinematicParameterStr) const {
1628 // ************************************************
1629  auto it = KinematicParameters->find(KinematicParameterStr);
1630  if (it != KinematicParameters->end()) return it->second;
1631 
1632  MACH3LOG_ERROR("Did not recognise Kinematic Parameter type: {}", KinematicParameterStr);
1633  throw MaCh3Exception(__FILE__, __LINE__);
1634 
1635  return M3::_BAD_INT_;
1636 }
1637 
1638 // ************************************************
1639 std::string SampleHandlerFD::ReturnStringFromKinematicParameter(const int KinematicParameter) const {
1640 // ************************************************
1641  auto it = ReversedKinematicParameters->find(KinematicParameter);
1642  if (it != ReversedKinematicParameters->end()) {
1643  return it->second;
1644  }
1645 
1646  MACH3LOG_ERROR("Did not recognise Kinematic Parameter type: {}", KinematicParameter);
1647  throw MaCh3Exception(__FILE__, __LINE__);
1648 
1649  return "";
1650 }
1651 
1652 // === JM define KinematicVector-to-string mapping functions ===
1653 // ************************************************
1654 int SampleHandlerFD::ReturnKinematicVectorFromString(const std::string& KinematicVectorStr) const {
1655 // ************************************************
1656  auto it = KinematicVectors->find(KinematicVectorStr);
1657  if (it != KinematicVectors->end()) return it->second;
1658 
1659  MACH3LOG_ERROR("Did not recognise Kinematic Vector: {}", KinematicVectorStr);
1660  throw MaCh3Exception(__FILE__, __LINE__);
1661 
1662  return M3::_BAD_INT_;
1663 }
1664 
1665 // ************************************************
1666 std::string SampleHandlerFD::ReturnStringFromKinematicVector(const int KinematicVector) const {
1667 // ************************************************
1668  auto it = ReversedKinematicVectors->find(KinematicVector);
1669  if (it != ReversedKinematicVectors->end()) {
1670  return it->second;
1671  }
1672 
1673  MACH3LOG_ERROR("Did not recognise Kinematic Vector: {}", KinematicVector);
1674  throw MaCh3Exception(__FILE__, __LINE__);
1675 
1676  return "";
1677 }
1678 
1679 // ************************************************
1680 std::vector<double> SampleHandlerFD::ReturnKinematicParameterBinning(const std::string& KinematicParameter) {
1681 // ************************************************
1682  // If x or y variable return used binning
1683  if(KinematicParameter == GetXBinVarName()) {
1684  return Binning.XBinEdges;
1685  } else if (KinematicParameter == GetYBinVarName()) {
1686  return Binning.YBinEdges;
1687  }
1688 
1689  auto MakeBins = [](int nBins) {
1690  std::vector<double> bins(nBins + 1);
1691  for (int i = 0; i <= nBins; ++i)
1692  bins[i] = static_cast<double>(i) - 0.5;
1693  return bins;
1694  };
1695 
1696  if (KinematicParameter == "OscillationChannel") {
1697  return MakeBins(GetNOscChannels());
1698  } else if (KinematicParameter == "Mode") {
1699  return MakeBins(Modes->GetNModes());
1700  }
1701 
1702  // We first check if binning for a sample has been specified
1703  auto BinningConfig = M3OpenConfig(SampleManager->raw()["BinningFile"].as<std::string>());
1704  if(BinningConfig[GetTitle()] && BinningConfig[GetTitle()][KinematicParameter]){
1705  auto BinningVect = Get<std::vector<double>>(BinningConfig[GetTitle()][KinematicParameter], __FILE__, __LINE__);
1706  return BinningVect;
1707  } else {
1708  auto BinningVect = Get<std::vector<double>>(BinningConfig[KinematicParameter], __FILE__, __LINE__);
1709  return BinningVect;
1710  }
1711 }
1712 
1713 
1714 bool SampleHandlerFD::IsSubEventVarString(const std::string& VarStr) {
1715  if (KinematicVectors == nullptr) return false;
1716 
1717  if (KinematicVectors->count(VarStr)) {
1718  if (!KinematicParameters->count(VarStr)) return true;
1719  else {
1720  MACH3LOG_ERROR("Attempted to plot kinematic variable {}, but it appears in both KinematicVectors and KinematicParameters", VarStr);
1721  throw MaCh3Exception(__FILE__,__LINE__);
1722  }
1723  }
1724  return false;
1725 }
1726 // ===============================================================
1727 
1728 TH1* SampleHandlerFD::Get1DVarHistByModeAndChannel(const std::string& ProjectionVar_Str,
1729  int kModeToFill, int kChannelToFill, int WeightStyle, TAxis* Axis) {
1730  bool fChannel;
1731  bool fMode;
1732 
1733  if (kChannelToFill != -1) {
1734  if (kChannelToFill > GetNOscChannels()) {
1735  MACH3LOG_ERROR("Required channel is not available. kChannelToFill should be between 0 and {}", GetNOscChannels());
1736  MACH3LOG_ERROR("kChannelToFill given:{}", kChannelToFill);
1737  MACH3LOG_ERROR("Exiting.");
1738  throw MaCh3Exception(__FILE__, __LINE__);
1739  }
1740  fChannel = true;
1741  } else {
1742  fChannel = false;
1743  }
1744 
1745  if (kModeToFill!=-1) {
1746  if (!(kModeToFill >= 0) && (kModeToFill < Modes->GetNModes())) {
1747  MACH3LOG_ERROR("Required mode is not available. kModeToFill should be between 0 and {}", Modes->GetNModes());
1748  MACH3LOG_ERROR("kModeToFill given:{}", kModeToFill);
1749  MACH3LOG_ERROR("Exiting..");
1750  throw MaCh3Exception(__FILE__, __LINE__);
1751  }
1752  fMode = true;
1753  } else {
1754  fMode = false;
1755  }
1756 
1757  std::vector< KinematicCut > SelectionVec;
1758 
1759  if (fMode) {
1760  KinematicCut SelecMode;
1762  SelecMode.LowerBound = kModeToFill;
1763  SelecMode.UpperBound = kModeToFill+1;
1764  SelectionVec.push_back(SelecMode);
1765  }
1766 
1767  if (fChannel) {
1768  KinematicCut SelecChannel;
1769  SelecChannel.ParamToCutOnIt = ReturnKinematicParameterFromString("OscillationChannel");
1770  SelecChannel.LowerBound = kChannelToFill;
1771  SelecChannel.UpperBound = kChannelToFill+1;
1772  SelectionVec.push_back(SelecChannel);
1773  }
1774 
1775  return Get1DVarHist(ProjectionVar_Str,SelectionVec,WeightStyle,Axis);
1776 }
1777 
1778 TH2* SampleHandlerFD::Get2DVarHistByModeAndChannel(const std::string& ProjectionVar_StrX, const std::string& ProjectionVar_StrY,
1779  int kModeToFill, int kChannelToFill, int WeightStyle, TAxis* AxisX, TAxis* AxisY) {
1780  bool fChannel;
1781  bool fMode;
1782 
1783  if (kChannelToFill!=-1) {
1784  if (kChannelToFill > GetNOscChannels()) {
1785  MACH3LOG_ERROR("Required channel is not available. kChannelToFill should be between 0 and {}", GetNOscChannels());
1786  MACH3LOG_ERROR("kChannelToFill given:{}", kChannelToFill);
1787  MACH3LOG_ERROR("Exiting.");
1788  throw MaCh3Exception(__FILE__, __LINE__);
1789  }
1790  fChannel = true;
1791  } else {
1792  fChannel = false;
1793  }
1794 
1795  if (kModeToFill!=-1) {
1796  if (!(kModeToFill >= 0) && (kModeToFill < Modes->GetNModes())) {
1797  MACH3LOG_ERROR("Required mode is not available. kModeToFill should be between 0 and {}", Modes->GetNModes());
1798  MACH3LOG_ERROR("kModeToFill given:{}", kModeToFill);
1799  MACH3LOG_ERROR("Exiting..");
1800  throw MaCh3Exception(__FILE__, __LINE__);
1801  }
1802  fMode = true;
1803  } else {
1804  fMode = false;
1805  }
1806 
1807  std::vector< KinematicCut > SelectionVec;
1808 
1809  if (fMode) {
1810  KinematicCut SelecMode;
1812  SelecMode.LowerBound = kModeToFill;
1813  SelecMode.UpperBound = kModeToFill+1;
1814  SelectionVec.push_back(SelecMode);
1815  }
1816 
1817  if (fChannel) {
1818  KinematicCut SelecChannel;
1819  SelecChannel.ParamToCutOnIt = ReturnKinematicParameterFromString("OscillationChannel");
1820  SelecChannel.LowerBound = kChannelToFill;
1821  SelecChannel.UpperBound = kChannelToFill+1;
1822  SelectionVec.push_back(SelecChannel);
1823  }
1824 
1825  return Get2DVarHist(ProjectionVar_StrX,ProjectionVar_StrY,SelectionVec,WeightStyle,AxisX,AxisY);
1826 }
1827 
1828 void SampleHandlerFD::PrintIntegral(const TString& OutputFileName, const int WeightStyle, const TString& OutputCSVFileName) {
1829  constexpr int space = 14;
1830 
1831  bool printToFile=false;
1832  if (OutputFileName.CompareTo("/dev/null")) {printToFile = true;}
1833 
1834  bool printToCSV=false;
1835  if(OutputCSVFileName.CompareTo("/dev/null")) printToCSV=true;
1836 
1837  std::ofstream outfile;
1838  if (printToFile) {
1839  outfile.open(OutputFileName.Data(), std::ios_base::app);
1840  outfile.precision(7);
1841  }
1842 
1843  std::ofstream outcsv;
1844  if(printToCSV){
1845  outcsv.open(OutputCSVFileName, std::ios_base::app); // Appened to CSV
1846  outcsv.precision(7);
1847  }
1848 
1849  double PDFIntegral = 0.;
1850 
1851  std::vector< std::vector< TH1* > > IntegralList;
1852  IntegralList.resize(Modes->GetNModes());
1853 
1854  std::vector<double> ChannelIntegral;
1855  ChannelIntegral.resize(GetNOscChannels());
1856  for (unsigned int i=0;i<ChannelIntegral.size();i++) {ChannelIntegral[i] = 0.;}
1857 
1858  for (int i=0;i<Modes->GetNModes();i++) {
1859  if (GetNDim()==1) {
1860  IntegralList[i] = ReturnHistsBySelection1D(GetXBinVarName(),1,i,WeightStyle);
1861  } else {
1862  IntegralList[i] = CastVector<TH2, TH1>(ReturnHistsBySelection2D(GetXBinVarName(),GetYBinVarName(),1,i,WeightStyle));
1863  }
1864  }
1865 
1866  MACH3LOG_INFO("-------------------------------------------------");
1867 
1868  if (printToFile) {
1869  outfile << "\\begin{table}[ht]" << std::endl;
1870  outfile << "\\begin{center}" << std::endl;
1871  outfile << "\\caption{Integral breakdown for sample: " << GetTitle() << "}" << std::endl;
1872  outfile << "\\label{" << GetTitle() << "-EventRate}" << std::endl;
1873 
1874  TString nColumns;
1875  for (int i=0;i<GetNOscChannels();i++) {nColumns+="|c";}
1876  nColumns += "|c|";
1877  outfile << "\\begin{tabular}{|l" << nColumns.Data() << "}" << std::endl;
1878  outfile << "\\hline" << std::endl;
1879  }
1880 
1881  if(printToCSV){
1882  // HW Probably a better way but oh well, here I go making MaCh3 messy again
1883  outcsv<<"Integral Breakdown for sample :"<<GetTitle()<<"\n";
1884  }
1885 
1886  MACH3LOG_INFO("Integral breakdown for sample: {}", GetTitle());
1887  MACH3LOG_INFO("");
1888 
1889  if (printToFile) {outfile << std::setw(space) << "Mode:";}
1890  if(printToCSV) {outcsv<<"Mode,";}
1891 
1892  std::string table_headings = fmt::format("| {:<8} |", "Mode");
1893  std::string table_footline = "------------"; //Scalable table horizontal line
1894  for (int i = 0;i < GetNOscChannels(); i++) {
1895  table_headings += fmt::format(" {:<17} |", GetFlavourName(i));
1896  table_footline += "--------------------";
1897  if (printToFile) {outfile << "&" << std::setw(space) << OscChannels[i].flavourName_Latex << " ";}
1898  if (printToCSV) {outcsv << GetFlavourName(i) << ",";}
1899  }
1900  if (printToFile) {outfile << "&" << std::setw(space) << "Total:" << "\\\\ \\hline" << std::endl;}
1901  if (printToCSV) {outcsv <<"Total\n";}
1902  table_headings += fmt::format(" {:<10} |", "Total");
1903  table_footline += "-------------";
1904 
1905  MACH3LOG_INFO("{}", table_headings);
1906  MACH3LOG_INFO("{}", table_footline);
1907 
1908  for (unsigned int i=0;i<IntegralList.size();i++) {
1909  double ModeIntegral = 0;
1910  if (printToFile) {outfile << std::setw(space) << Modes->GetMaCh3ModeName(i);}
1911  if(printToCSV) {outcsv << Modes->GetMaCh3ModeName(i) << ",";}
1912 
1913  table_headings = fmt::format("| {:<8} |", Modes->GetMaCh3ModeName(i)); //Start string with mode name
1914 
1915  for (unsigned int j=0;j<IntegralList[i].size();j++) {
1916  double Integral = IntegralList[i][j]->Integral();
1917 
1918  if (Integral<1e-100) {Integral=0;}
1919 
1920  ModeIntegral += Integral;
1921  ChannelIntegral[j] += Integral;
1922  PDFIntegral += Integral;
1923 
1924  if (printToFile) {outfile << "&" << std::setw(space) << Form("%4.5f",Integral) << " ";}
1925  if (printToCSV) {outcsv << Form("%4.5f", Integral) << ",";}
1926 
1927  table_headings += fmt::format(" {:<17.4f} |", Integral);
1928  }
1929  if (printToFile) {outfile << "&" << std::setw(space) << Form("%4.5f",ModeIntegral) << " \\\\ \\hline" << std::endl;}
1930  if (printToCSV) {outcsv << Form("%4.5f", ModeIntegral) << "\n";}
1931 
1932  table_headings += fmt::format(" {:<10.4f} |", ModeIntegral);
1933 
1934  MACH3LOG_INFO("{}", table_headings);
1935  }
1936 
1937  if (printToFile) {outfile << std::setw(space) << "Total:";}
1938  if (printToCSV) {outcsv << "Total,";}
1939 
1940  //Clear the table_headings to print last row of totals
1941  table_headings = fmt::format("| {:<8} |", "Total");
1942  for (unsigned int i=0;i<ChannelIntegral.size();i++) {
1943  if (printToFile) {outfile << "&" << std::setw(space) << Form("%4.5f",ChannelIntegral[i]) << " ";}
1944  if (printToCSV) {outcsv << Form("%4.5f", ChannelIntegral[i]) << ",";}
1945  table_headings += fmt::format(" {:<17.4f} |", ChannelIntegral[i]);
1946  }
1947  if (printToFile) {outfile << "&" << std::setw(space) << Form("%4.5f",PDFIntegral) << " \\\\ \\hline" << std::endl;}
1948  if (printToCSV) {outcsv << Form("%4.5f", PDFIntegral) << "\n\n\n\n";} // Let's have a few new lines!
1949 
1950  table_headings += fmt::format(" {:<10.4f} |", PDFIntegral);
1951  MACH3LOG_INFO("{}", table_headings);
1952  MACH3LOG_INFO("{}", table_footline);
1953 
1954  if (printToFile) {
1955  outfile << "\\end{tabular}" << std::endl;
1956  outfile << "\\end{center}" << std::endl;
1957  outfile << "\\end{table}" << std::endl;
1958  }
1959 
1960  MACH3LOG_INFO("");
1961 
1962  if (printToFile) {
1963  outfile << std::endl;
1964  outfile.close();
1965  }
1966  // KS: Clean memory we could use smart pointers in future
1967  CleanContainer(IntegralList);
1968 }
1969 
1970 std::vector<TH1*> SampleHandlerFD::ReturnHistsBySelection1D(std::string KinematicProjection, int Selection1, int Selection2, int WeightStyle, TAxis* XAxis) {
1971  std::vector<TH1*> hHistList;
1972  std::string legendEntry;
1973 
1974  if (THStackLeg != nullptr) {delete THStackLeg;}
1975  THStackLeg = new TLegend(0.1,0.1,0.9,0.9);
1976 
1977  int iMax = -1;
1978  if (Selection1 == FDPlotType::kModePlot) {
1979  iMax = Modes->GetNModes();
1980  }
1981  if (Selection1 == FDPlotType::kOscChannelPlot) {
1982  iMax = GetNOscChannels();
1983  }
1984  if (iMax == -1) {
1985  MACH3LOG_ERROR("You've passed me a Selection1 which was not implemented in ReturnHistsBySelection1D. Selection1 and Selection2 are counters for different indexable quantities");
1986  throw MaCh3Exception(__FILE__, __LINE__);
1987  }
1988 
1989  for (int i=0;i<iMax;i++) {
1990  if (Selection1 == FDPlotType::kModePlot) {
1991  hHistList.push_back(Get1DVarHistByModeAndChannel(KinematicProjection,i,Selection2,WeightStyle,XAxis));
1992  THStackLeg->AddEntry(hHistList[i],(Modes->GetMaCh3ModeName(i)+Form(" : (%4.2f)",hHistList[i]->Integral())).c_str(),"f");
1993 
1994  hHistList[i]->SetFillColor(static_cast<Color_t>(Modes->GetMaCh3ModePlotColor(i)));
1995  hHistList[i]->SetLineColor(static_cast<Color_t>(Modes->GetMaCh3ModePlotColor(i)));
1996  }
1997  if (Selection1 == FDPlotType::kOscChannelPlot) {
1998  hHistList.push_back(Get1DVarHistByModeAndChannel(KinematicProjection,Selection2,i,WeightStyle,XAxis));
1999  THStackLeg->AddEntry(hHistList[i],(GetFlavourName(i)+Form(" | %4.2f",hHistList[i]->Integral())).c_str(),"f");
2000  }
2001  }
2002 
2003  return hHistList;
2004 }
2005 // ************************************************
2006 std::vector<TH2*> SampleHandlerFD::ReturnHistsBySelection2D(std::string KinematicProjectionX, std::string KinematicProjectionY,
2007  int Selection1, int Selection2, int WeightStyle,
2008  TAxis* XAxis, TAxis* YAxis) {
2009 // ************************************************
2010  std::vector<TH2*> hHistList;
2011 
2012  int iMax = -1;
2013  if (Selection1 == FDPlotType::kModePlot) {
2014  iMax = Modes->GetNModes();
2015  }
2016  if (Selection1 == FDPlotType::kOscChannelPlot) {
2017  iMax = GetNOscChannels();
2018  }
2019  if (iMax == -1) {
2020  MACH3LOG_ERROR("You've passed me a Selection1 which was not implemented in ReturnHistsBySelection1D. Selection1 and Selection2 are counters for different indexable quantities");
2021  throw MaCh3Exception(__FILE__, __LINE__);
2022  }
2023 
2024  for (int i=0;i<iMax;i++) {
2025  if (Selection1 == FDPlotType::kModePlot) {
2026  hHistList.push_back(Get2DVarHistByModeAndChannel(KinematicProjectionX,KinematicProjectionY,i,Selection2,WeightStyle,XAxis,YAxis));
2027  }
2028  if (Selection1 == FDPlotType::kOscChannelPlot) {
2029  hHistList.push_back(Get2DVarHistByModeAndChannel(KinematicProjectionX,KinematicProjectionY,Selection2,i,WeightStyle,XAxis,YAxis));
2030  }
2031  }
2032 
2033  return hHistList;
2034 }
2035 
2036 THStack* SampleHandlerFD::ReturnStackedHistBySelection1D(std::string KinematicProjection, int Selection1, int Selection2, int WeightStyle, TAxis* XAxis) {
2037  std::vector<TH1*> HistList = ReturnHistsBySelection1D(KinematicProjection, Selection1, Selection2, WeightStyle, XAxis);
2038  THStack* StackHist = new THStack((GetTitle()+"_"+KinematicProjection+"_Stack").c_str(),"");
2039  for (unsigned int i=0;i<HistList.size();i++) {
2040  StackHist->Add(HistList[i]);
2041  }
2042  return StackHist;
2043 }
2044 
2045 
2046 // ************************************************
2047 const double* SampleHandlerFD::GetPointerToOscChannel(int iEvent) const {
2048 // ************************************************
2049  const int Channel = GetOscChannel(OscChannels, (*MCSamples[iEvent].nupdgUnosc), (*MCSamples[iEvent].nupdg));
2050 
2051  return &(OscChannels[Channel].ChannelIndex);
2052 }
int GetOscChannel(const std::vector< OscChannelInfo > &OscChannel, const int InitFlav, const int FinalFlav)
KS: Get Osc Channel Index based on initial and final PDG codes.
int size
KS: Based on this https://github.com/gabime/spdlog/blob/a2b4262090fd3f005c2315dcb5be2f0f1774a005/incl...
#define MACH3LOG_DEBUG
Definition: MaCh3Logger.h:24
#define MACH3LOG_ERROR
Definition: MaCh3Logger.h:27
#define MACH3LOG_INFO
Definition: MaCh3Logger.h:25
#define MACH3LOG_WARN
Definition: MaCh3Logger.h:26
#define MACH3LOG_TRACE
Definition: MaCh3Logger.h:23
std::function< void(const double *, std::size_t)> FuncParFuncType
void CleanContainer(std::vector< T * > &container)
Generic cleanup function.
NuPDG
Enum to track the incoming neutrino species.
TestStatistic
Make an enum of the test statistic that we're using.
Type GetFromManager(const YAML::Node &node, Type defval, const std::string File="", const int Line=1)
Get content of config file if node is not found take default value specified.
Definition: YamlHelper.h:299
TMacro YAMLtoTMacro(const YAML::Node &yaml_node, const std::string &name)
Convert a YAML node to a ROOT TMacro object.
Definition: YamlHelper.h:162
bool CheckNodeExists(const YAML::Node &node, Args... args)
KS: Wrapper function to call the recursive helper.
Definition: YamlHelper.h:55
#define M3OpenConfig(filename)
Macro to simplify calling LoadYaml with file and line info.
Definition: YamlHelper.h:561
#define GetBounds(filename)
Definition: YamlHelper.h:562
Custom exception class for MaCh3 errors.
const double * RetPointer(const int iParam)
DB Pointer return to param position.
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 ...
std::unique_ptr< MaCh3Modes > Modes
Holds information about used Generator and MaCh3 modes.
TestStatistic fTestStatistic
Test statistic tells what kind of likelihood sample is using.
M3::int_t nSamples
Contains how many samples we've got.
unsigned int nEvents
Number of MC events are there.
bool MatchCondition(const std::vector< T > &allowedValues, const T &value)
check if event is affected by following conditions, for example pdg, or modes etc
std::vector< std::vector< int > > funcParsGrid
HH - a grid of vectors of enums for each sample and event.
void CalcNormsBins(std::vector< NormParameter > &norm_parameters, std::vector< std::vector< int > > &xsec_norms_bins)
Check whether a normalisation systematic affects an event or not.
std::vector< FunctionalParameter > funcParsVec
HH - a vector that stores all the FuncPars struct.
virtual void resetShifts(int iEvent)
HH - reset the shifted values to the original values.
std::vector< FunctionalParameter * > funcParsMap
HH - a map that relates the funcpar enum to pointer of FuncPars struct HH - Changed to a vector of po...
virtual void Init()=0
Initialise any variables that your experiment specific SampleHandler needs.
std::string ReturnStringFromKinematicVector(const int KinematicVariable) const
std::vector< double > ReturnKinematicParameterBinning(const std::string &KinematicParameter)
Return the binning used to draw a kinematic parameter.
void AddData(TH1D *Data)
std::unique_ptr< BinnedSplineHandler > SplineHandler
Contains all your binned splines and handles the setup and the returning of weights from spline evalu...
virtual void CalcWeightFunc(int iEvent)
Calculate weights for function parameters.
SampleBinningInfo Binning
KS: This stores binning information, in future could be come vector to store binning for every used s...
void PrintIntegral(const TString &OutputName="/dev/null", const int WeightStyle=0, const TString &OutputCSVName="/dev/null")
virtual void SetupSplines()=0
initialise your splineXX object and then use InitialiseSplineObject to conviently setup everything up
SampleInfo SampleDetails
Stores info about currently initialised sample.
bool IsEventSelected(const int iEvent)
DB Function which determines if an event is selected, where Selection double looks like {{ND280Kinema...
ParameterHandlerGeneric * ParHandler
bool FirstTimeW2
KS:Super hacky to update W2 or not.
std::unordered_map< std::string, NuPDG > FileToInitPDGMap
std::vector< KinematicCut > Selection
a way to store selection cuts which you may push back in the get1DVar functions most of the time this...
double * SampleHandlerFD_data
DB Array to be filled in AddData.
virtual int SetupExperimentMC()=0
Experiment specific setup, returns the number of events which were loaded.
const std::unordered_map< int, std::string > * ReversedKinematicVectors
void FillMCHist(const int Dimension)
Fill a histogram with the event-level information used in the fit.
std::vector< KinematicCut > StoredSelection
What gets pulled from config options, these are constant after loading in this is of length 3: 0th in...
std::vector< std::string > spline_files
names of spline files associated associated with this object
double GetLikelihood() override
DB Multi-threaded GetLikelihood.
std::unordered_map< std::string, NuPDG > FileToFinalPDGMap
std::unordered_map< int, FuncParFuncType > funcParsFuncMap
HH - a map that relates the funcpar enum to pointer of the actual function.
const double * GetPointerToOscChannel(const int iEvent) const
Get pointer to oscillation channel associated with given event. Osc channel is const.
std::vector< OscChannelInfo > OscChannels
Stores info about oscillation channel for a single sample.
void FindNominalBinAndEdges2D()
const std::unordered_map< std::string, int > * KinematicVectors
void SetupNormParameters()
Setup the norm parameters by assigning each event with bin.
void SetupKinematicMap()
Ensure Kinematic Map is setup and make sure it is initialised correctly.
void FillSplineBins()
Finds the binned spline that an event should apply to and stored them in a a vector for easy evaluati...
virtual void PrepFunctionalParameters()
Update the functional parameter values to the latest proposed values. Needs to be called before every...
SampleHandlerFD(std::string ConfigFileName, ParameterHandlerGeneric *xsec_cov, const std::shared_ptr< OscillationHandler > &OscillatorObj_=nullptr)
Constructor.
void Fill2DSubEventHist(TH2D *_h2DVar, const std::string &ProjectionVarX, const std::string &ProjectionVarY, const std::vector< KinematicCut > &SubEventSelectionVec=std::vector< KinematicCut >(), int WeightStyle=0)
void Initialise()
Function which does a lot of the lifting regarding the workflow in creating different MC objects.
virtual ~SampleHandlerFD()
destructor
void FindNominalBinAndEdges1D()
double * SampleHandlerFD_array
DB Array to be filled after reweighting.
std::vector< std::string > funcParsNamesVec
HH - a vector of string names for each functional parameter.
virtual const double * GetPointerToKinematicParameter(std::string KinematicParamter, int iEvent)=0
virtual void SetupFDMC()=0
Function which translates experiment struct into core struct.
void SaveAdditionalInfo(TDirectory *Dir) override
Store additional info in a chan.
std::unique_ptr< manager > SampleManager
The manager object used to read the sample yaml file.
M3::float_t GetEventWeight(const int iEntry) const
void InitialiseNuOscillatorObjects()
including Dan's magic NuOscillator
void RegisterIndividualFunctionalParameter(const std::string &fpName, int fpEnum, FuncParFuncType fpFunc)
HH - a helper function for RegisterFunctionalParameter.
M3::float_t CalcWeightNorm(const FarDetectorCoreInfo *MCEvent) const
Calculate the norm weight for a given event.
bool UpdateW2
KS:Super hacky to update W2 or not.
std::shared_ptr< OscillationHandler > Oscillator
Contains all your binned splines and handles the setup and the returning of weights from spline evalu...
bool IsSubEventSelected(const std::vector< KinematicCut > &SubEventCuts, const int iEvent, unsigned const int iSubEvent, size_t nsubevents)
JM Function which determines if a subevent is selected.
const std::unordered_map< std::string, int > * KinematicParameters
Mapping between string and kinematic enum.
void Set2DBinning(size_t nbins1, double *boundaries1, size_t nbins2, double *boundaries2)
set the binning for 2D sample used for the likelihood calculation
void InitialiseSingleFDMCObject()
function to create the member of the FarDetectorInfo struct so they are the appropriate size.
TH2 * Get2DVarHist(const std::string &ProjectionVarX, const std::string &ProjectionVarY, const std::vector< KinematicCut > &EventSelectionVec=std::vector< KinematicCut >(), int WeightStyle=0, TAxis *AxisX=nullptr, TAxis *AxisY=nullptr, const std::vector< KinematicCut > &SubEventSelectionVec=std::vector< KinematicCut >())
virtual void RegisterFunctionalParameters()=0
HH - a experiment-specific function where the maps to actual functions are set up.
M3::float_t CalcWeightSpline(const FarDetectorCoreInfo *MCEvent) const
Calculate the spline weight for a given event.
std::vector< std::string > mc_files
names of mc files associated associated with this object
void ResetHistograms()
Helper function to reset histograms.
virtual double ReturnKinematicParameter(std::string KinematicParamter, int iEvent)=0
Return the value of an associated kinematic parameter for an event.
bool IsSubEventVarString(const std::string &VarStr)
JM Check if a kinematic parameter string corresponds to a subevent-level variable.
virtual void ApplyShifts(int iEvent)
ETA - generic function applying shifts.
std::unordered_map< std::string, double > _modeNomWeightMap
const std::unordered_map< int, std::string > * ReversedKinematicParameters
Mapping between kinematic enum and string.
int ReturnKinematicVectorFromString(const std::string &KinematicStr) const
virtual void SetupWeightPointers()=0
DB Function to determine which weights apply to which types of samples pure virtual!...
void SetupSampleBinning()
Function to setup the binning of your sample histograms and the underlying arrays that get handled in...
void Reweight() override
void SetupNuOscillatorPointers()
virtual std::vector< double > ReturnKinematicVector(std::string KinematicParameter, int iEvent)
virtual void SetupFunctionalParameters()
ETA - a function to setup and pass values to functional parameters where you need to pass a value to ...
void FillArray()
DB Nice new multi-threaded function which calculates the event weights and fills the relevant bins of...
double * SampleHandlerFD_array_w2
KS Array used for MC stat.
TLegend * THStackLeg
DB Miscellaneous Variables.
std::vector< FarDetectorCoreInfo > MCSamples
Stores information about every MC event.
void SetupReweightArrays(const size_t numberXBins, const size_t numberYBins)
Initialise data, MC and W2 histograms.
std::unordered_map< std::string, int > funcParsNamesMap
HH - a map that relates the name of the functional parameter to funcpar enum.
void Set1DBinning(size_t nbins, double *boundaries)
sets the binning used for the likelihood calculation, used for both data and MC
std::string SampleName
A unique ID for each sample based on which we can define what systematic should be applied.
const std::vector< NormParameter > GetNormParsFromSampleName(const std::string &SampleName) const
DB Get norm/func parameters depending on given SampleName.
std::string GetParFancyName(const int i) const
Get fancy name of the Parameter.
const std::vector< FunctionalParameter > GetFunctionalParametersFromSampleName(const std::string &SampleName) const
HH Get functional parameters for the relevant SampleName.
std::vector< const double * > GetOscParsFromSampleName(const std::string &SampleName)
Get pointers to Osc params from Sample name.
std::string GetXBinVarName() const
std::string GetYBinVarName() const
TH1 * Get1DVarHist(const std::string &ProjectionVar, const std::vector< KinematicCut > &EventSelectionVec=std::vector< KinematicCut >(), int WeightStyle=0, TAxis *Axis=nullptr, const std::vector< KinematicCut > &SubEventSelectionVec=std::vector< KinematicCut >())
std::string ReturnStringFromKinematicParameter(const int KinematicVariable) const
ETA function to generically convert a kinematic type from xsec cov to a string.
std::string GetSampleName(int iSample=0) const override
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....
void Fill1DSubEventHist(TH1D *_h1DVar, const std::string &ProjectionVar, const std::vector< KinematicCut > &SubEventSelectionVec=std::vector< KinematicCut >(), int WeightStyle=0)
int GetNDim() const
DB Function to differentiate 1D or 2D binning.
unsigned int GetNEvents() const
std::vector< TH2 * > ReturnHistsBySelection2D(std::string KinematicProjectionX, std::string KinematicProjectionY, int Selection1, int Selection2=-1, int WeightStyle=0, TAxis *XAxis=0, TAxis *YAxis=0)
std::string GetTitle() const override
std::string GetFlavourName(const int iChannel)
int GetNOscChannels() override
TH2 * Get2DVarHistByModeAndChannel(const std::string &ProjectionVar_StrX, const std::string &ProjectionVar_StrY, int kModeToFill=-1, int kChannelToFill=-1, int WeightStyle=0, TAxis *AxisX=nullptr, TAxis *AxisY=nullptr)
TH1 * GetW2Hist(const int Dimension)
Get W2 histogram.
TH1 * Get1DVarHistByModeAndChannel(const std::string &ProjectionVar_Str, int kModeToFill=-1, int kChannelToFill=-1, int WeightStyle=0, TAxis *Axis=nullptr)
TH1 * GetMCHist(const int Dimension)
Get MC histogram.
std::vector< TH1 * > ReturnHistsBySelection1D(std::string KinematicProjection, int Selection1, int Selection2=-1, int WeightStyle=0, TAxis *Axis=0)
int ReturnKinematicParameterFromString(const std::string &KinematicStr) const
ETA function to generically convert a string from xsec cov to a kinematic type.
TH1 * GetDataHist(const int Dimension)
Get Data histogram.
THStack * ReturnStackedHistBySelection1D(std::string KinematicProjection, int Selection1, int Selection2=-1, int WeightStyle=0, TAxis *Axis=0)
double float_t
Definition: Core.h:30
constexpr static const double _BAD_DOUBLE_
Default value used for double initialisation.
Definition: Core.h:46
constexpr static const float_t Unity
Definition: Core.h:57
constexpr static const float_t Zero
Definition: Core.h:64
constexpr static const int _BAD_INT_
Default value used for int initialisation.
Definition: Core.h:48
int int_t
Definition: Core.h:31
int PDGToNuOscillatorFlavour(const int NuPdg)
Convert from PDG flavour to NuOscillator type beware that in the case of anti-neutrinos the NuOscilla...
constructors are same for all three so put in here
std::vector< const double * > xsec_norm_pointers
Pointers to normalisation weights which are being taken from Parameter Handler.
M3::float_t xsec_w
Total weight of norm and spline parameters.
std::vector< const M3::float_t * > xsec_spline_pointers
Pointers to spline weights which are being calculated by Splines Handler.
HH - Functional parameters Carrier for whether you want to apply a systematic to an event or not.
const double * valuePtr
Parameter value pointer.
FuncParFuncType * funcPtr
Function pointer.
KS: Small struct used for applying kinematic cuts.
double UpperBound
Upper bound on which we apply cut.
double LowerBound
Lower bound on which we apply cut.
int ParamToCutOnIt
Index or enum value identifying the kinematic variable to cut on.
KS: Store info about used osc channels.
std::string flavourName
Name of osc channel.
int GetBin(const int xBin, const int yBin) const
Get linear bin index from 2D bin indices.
void InitialiseBinMigrationLookUp()
Initialise special lookup arrays allowing to more efficiently perform bin-migration These arrays stor...
std::vector< double > YBinEdges
Vector to hold y-axis bin-edges.
size_t nXBins
Number of X axis bins in the histogram used for likelihood calculation.
size_t nYBins
Number of Y axis bins in the histogram used for likelihood calculation.
int GetBinSafe(const int xBin, const int yBin) const
Get linear bin index from 2D bin indices with additional checks.
size_t GlobalOffset
If you have binning for multiple samples and trying to define 1D vector let's.
std::vector< double > XBinEdges
Vector to hold x-axis bin-edges.
size_t nBins
Number of total bins.
int FindXBin(const double XVar, const int NomXBin) const
DB Find the relevant bin in the PDF for each event.
TH1D * _hPDF1D
histogram used for plotting storing 1D MC distribution
void InitialiseHistograms()
Initialise histograms used for plotting.
TH2D * _hPDF2D
histogram used for plotting storing 2D MC distribution
int nDimensions
Keep track of the dimensions of the sample binning.
std::string XVarStr
the strings associated with the variables used for the X binning e.g. "RecoNeutrinoEnergy"
std::string SampleTitle
the name of this sample e.g."muon-like"
TH2D * dathist2d
histogram used for plotting storing 2D data distribution
TH1D * dathist
histogram used for plotting storing 1D data distribution
std::string YVarStr
the strings associated with the variables used for the Y binning e.g. "RecoNeutrinoEnergy"