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