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