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