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