MaCh3  2.5.0
Reference Guide
Classes | Functions
samples.h File Reference
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/numpy.h>
#include "Samples/SampleHandlerBase.h"
#include "TH1.h"
#include "TH2.h"
Include dependency graph for samples.h:
This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Classes

class  PySampleHandlerInterface
 EW: As SampleHandlerBase is an abstract base class we have to do some gymnastics to get it to get it into python. More...
 
class  PySampleHandlerBase
 As SampleHandlerBase is an abstract base class we have to do some gymnastics to get it to get it into python. More...
 

Functions

std::tuple< py::array_t< M3::float_t >, py::array_t< M3::float_t > > TH1ToNumpy (const TH1 *hist)
 
std::tuple< py::array_t< M3::float_t >, py::array_t< M3::float_t >, py::array_t< M3::float_t > > TH2ToNumpy (const TH2 *hist)
 
void initSamplesModule (py::module &m_samples)
 

Detailed Description

Author
Ewan Miller

Definition in file samples.h.

Function Documentation

◆ initSamplesModule()

void initSamplesModule ( py::module &  m_samples)
Todo:
Deal with non uniform binning
Todo:
Deal with higher dimensions MaCh3 returns flattened bins, will need to figure out how to pass bin edge info into python
Todo:
Deal with non uniform binning
Todo:
Deal with non uniform binning
Todo:
Deal with higher dimensions MaCh3 returns flattened bins, will need to figure out how to pass bin edge info into python
Todo:
Deal with non uniform binning
Todo:
Deal with higher dimensions MaCh3 returns flattened bins, will need to figure out how to pass bin edge info into python

Definition at line 423 of file samples.h.

423  {
424 
425  // Bind the systematic type enum that lets us set different types of systematics
426  py::enum_<TestStatistic>(m_samples, "TestStatistic")
427  .value("Poisson", TestStatistic::kPoisson)
428  .value("Barlow_Beeston", TestStatistic::kBarlowBeeston)
429  .value("Ice_Cube", TestStatistic::kIceCube)
430  .value("Pearson", TestStatistic::kPearson)
431  .value("Dembinski_Abdelmottele", TestStatistic::kDembinskiAbdelmotteleb)
432  .value("N_Test_Statistics", TestStatistic::kNTestStatistics);
433 
434  py::class_<SampleHandlerInterface, PySampleHandlerInterface /* <--- trampoline*/>(m_samples, "SampleHandlerInterface")
435  .def(py::init())
436 
437  .def(
438  "reweight",
440  "reweight the MC events in this sample. You will need to override this."
441  )
442 
443  .def(
444  "get_likelihood",
446  "Get the sample likelihood at the current point in your model space. You will need to override this."
447  )
448 
449  .def(
450  "set_test_stat",
452  "Set the test statistic that should be used when calculating likelihoods. \n\
453  :param test_stat: The new test statistic to use",
454  py::arg("test_stat")
455  )
456 
457  .def(
458  "get_bin_LLH",
459  py::overload_cast<double, double, double>(&SampleHandlerInterface::GetTestStatLLH, py::const_),
460  "Get the LLH for a bin by comparing the data and MC. The result depends on having previously set the test statistic using :py:meth:`pyMaCh3.samples.SampleHandlerInterface.set_test_stat` \n\
461  :param data: The data content of the bin. \n\
462  :param mc: The mc content of the bin \n\
463  :param w2: The Sum(w_{i}^2) (sum of weights squared) in the bin, which is sigma^2_{MC stats}",
464  py::arg("data"),
465  py::arg("mc"),
466  py::arg("w2")
467  )
468  ; // End of SampleHandlerInterface binding
469 
470  py::class_<SampleHandlerBase, PySampleHandlerBase /* <--- trampoline*/,
471  SampleHandlerInterface>(m_samples, "SampleHandlerBase")
472  .def(
473  py::init<std::string, ParameterHandlerGeneric*>(),
474  "This should never be called directly as SampleHandlerBase is an abstract base class. \n\
475  However when creating a derived class, in the __init__() method, you should call the parent constructor i.e. this one by doing:: \n\
476  \n\
477  \tsuper(<your derived SampleHandler class>, self).__init__(*args) \n\
478  \n ",
479  py::arg("mc_version"), py::arg("xsec_cov"))
480 
481  .def(
482  "get_mc_hist",
483  [](SampleHandlerBase &self, const int sample) {
484 
485  int Dimension = self.GetNDim(sample);
486 
487  //self.Reweight();
488 
489  // Get the histogram pointer BEFORE cloning
490  const TH1 *hist_original = self.GetMCHist(sample);
491 
492  // Debug: Check the original histogram
493  if (!hist_original) {
494  throw std::runtime_error("GetMCHist returned null pointer");
495  }
496 
497  // Now clone it
498  TH1D *hist = static_cast<TH1D*>(hist_original->Clone("cloned_hist"));
499 
500  if (Dimension == 1) {
501  // 1D histogram
502  auto [contents, edgesX] = TH1ToNumpy(hist);
503  auto edgesY = py::array_t<M3::float_t>();
504  return py::make_tuple(contents, edgesX, edgesY);
505  } else if (Dimension == 2) {
506 
507  TH2Poly *hist2poly = dynamic_cast<TH2Poly *>(hist);
508  if (hist2poly) {
510  throw std::runtime_error("pyMaCh3 can't do non-uniform binning for now :(");
511  }
512 
513  // 2D histogram - cast to TH2
514  TH2 *hist2d = dynamic_cast<TH2 *>(hist);
515  if (!hist2d) {
516  throw std::runtime_error("Failed to cast to TH2");
517  }
518  auto [contents, edgesX, edgesY] = TH2ToNumpy(hist2d);
519  return py::make_tuple(contents, edgesX, edgesY);
520  } else {
524  throw std::invalid_argument("Dimension must be 1 or 2");
525  }
526  },
527  py::return_value_policy::reference_internal,
528  py::arg("sample"),
529  "Get MC histogram as numpy arrays.\n"
530  "For 1D: Returns (contents, edges)\n"
531  "For 2D: Returns (contents, edgesX, edgesY)\n"
532  "where contents is shape (nbinsY, nbinsX) for 2D")
533 
534  .def(
535  "get_data_hist",
536  [](SampleHandlerBase &self, const int sample) {
537  const TH1 *hist = self.GetDataHist(sample);
538 
539  int Dimension = self.GetNDim(sample);
540 
541  if (Dimension == 1) {
542  // 1D histogram
543  const auto [contents, edgesX] = TH1ToNumpy(hist);
544  const auto edgesY = py::array_t<M3::float_t>();
545  return py::make_tuple(contents, edgesX, edgesY);
546  } else if (Dimension == 2) {
547 
548  const TH2Poly *hist2poly = dynamic_cast<const TH2Poly *>(hist);
549  if (hist2poly) {
551  throw std::runtime_error("pyMaCh3 can't do non-uniform binning for now :(");
552  }
553 
555  throw std::runtime_error("pyMaCh3 can't do non-uniform binning for now :(");
556 
557  // 2D histogram - cast to TH2
558  const TH2 *hist2d = dynamic_cast<const TH2 *>(hist);
559  if (!hist2d) {
560  throw std::runtime_error("Failed to cast to TH2");
561  }
562  const auto [contents, edgesX, edgesY] = TH2ToNumpy(hist2d);
563  return py::make_tuple(contents, edgesX, edgesY);
564  } else {
568  throw std::invalid_argument("Dimension must be 1 or 2");
569  }
570  },
571  py::arg("Dimension"),
572  "Get Data histogram as numpy arrays.\n"
573  "For 1D: Returns (contents, edges)\n"
574  "For 2D: Returns (contents, edgesX, edgesY)\n"
575  "where contents is shape (nbinsY, nbinsX) for 2D")
576 
577  .def(
578  "get_w2_hist",
579  [](SampleHandlerBase &self, const int sample) {
580  const TH1 *hist = self.GetW2Hist(sample);
581 
582  int Dimension = self.GetNDim(sample);
583 
584  if (Dimension == 1) {
585  // 1D histogram
586  const auto [contents, edgesX] = TH1ToNumpy(hist);
587  const auto edgesY = py::array_t<M3::float_t>();
588  return py::make_tuple(contents, edgesX, edgesY);
589  } else if (Dimension == 2) {
590 
591  const TH2Poly *hist2poly = dynamic_cast<const TH2Poly *>(hist);
592  if (hist2poly) {
594  throw std::runtime_error("pyMaCh3 can't do non-uniform binning for now :(");
595  }
596 
597  // 2D histogram - cast to TH2
598  const TH2 *hist2d = dynamic_cast<const TH2 *>(hist);
599  if (!hist2d) {
600  throw std::runtime_error("Failed to cast to TH2");
601  }
602  const auto [contents, edgesX, edgesY] = TH2ToNumpy(hist2d);
603  return py::make_tuple(contents, edgesX, edgesY);
604  } else {
608  throw std::invalid_argument("Dimension must be 1 or 2");
609  }
610  },
611  py::arg("sample"),
612  "Get W2 histogram as numpy arrays.\n"
613  "For 1D: Returns (contents, edges)\n"
614  "For 2D: Returns (contents, edgesX, edgesY)\n"
615  "where contents is shape (nbinsY, nbinsX) for 2D");
616 
617  /* Not sure if this will be needed in future versions of MaCh3 so leaving commented for now
618  py::class_<fdmc_base>(m_samples, "MCstruct")
619  .def(py::init())
620 
621  // Because a lot of the variables in fdmc_base use c style arrays,
622  // we need to provide some setter functions to be able to set them using more
623  // "pythony" objects, e.g. lists and numpy arrays
624  .def(
625  "set_event_variable_values",
626  [](fdmc_base &self, int dim, py::array_t<M3::float_t, py::array::c_style> &array)
627  {
628  py::buffer_info bufInfo = array.request();
629 
630  if ( dim > 2 )
631  throw MaCh3Exception(__FILE__, __LINE__, "Currently only dimensions of 1 or 2 are supported sorry :(");
632 
633  if ( bufInfo.ndim != 1 )
634  throw MaCh3Exception(__FILE__, __LINE__, "Number of dimensions in parameter array must be one if setting only one of the event variable arrays!");
635 
636  if( dim ==1 )
637  self.x_var = array.data();
638 
639  else if ( dim == 2)
640  self.y_var = array.data();
641  }
642  )
643  ;
644  */
645 }
@ kNTestStatistics
Number of test statistics.
@ kPearson
Standard Pearson likelihood .
@ kBarlowBeeston
Barlow-Beeston () following Conway approximation ()
@ kIceCube
Based on .
@ kDembinskiAbdelmotteleb
Based on .
@ kPoisson
Standard Poisson likelihood .
As SampleHandlerBase is an abstract base class we have to do some gymnastics to get it to get it into...
Definition: samples.h:314
EW: As SampleHandlerBase is an abstract base class we have to do some gymnastics to get it to get it ...
Definition: samples.h:100
Class responsible for handling implementation of samples used in analysis, reweighting and returning ...
Class responsible for handling implementation of samples used in analysis, reweighting and returning ...
virtual double GetLikelihood() const =0
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....
virtual void Reweight()=0
void SetTestStatistic(TestStatistic testStat)
Set the test statistic to be used when calculating the binned likelihoods.
std::tuple< py::array_t< M3::float_t >, py::array_t< M3::float_t > > TH1ToNumpy(const TH1 *hist)
Definition: samples.h:17
std::tuple< py::array_t< M3::float_t >, py::array_t< M3::float_t >, py::array_t< M3::float_t > > TH2ToNumpy(const TH2 *hist)
Definition: samples.h:50

◆ TH1ToNumpy()

std::tuple<py::array_t<M3::float_t>, py::array_t<M3::float_t> > TH1ToNumpy ( const TH1 *  hist)

Definition at line 17 of file samples.h.

17  {
18  if (!hist) {
19  throw std::runtime_error("Histogram pointer is null");
20  }
21 
22  int nbins = hist->GetNbinsX();
23 
24  // Create numpy array for bin contents
25  py::array_t<M3::float_t> contents(nbins);
26  auto contents_buf = contents.request();
27  M3::float_t* contents_ptr = static_cast<M3::float_t*>(contents_buf.ptr);
28 
29  // Create numpy array for bin edges (nbins + 1 edges)
30  py::array_t<M3::float_t> edges(nbins + 1);
31  auto edges_buf = edges.request();
32  M3::float_t* edges_ptr = static_cast<M3::float_t*>(edges_buf.ptr);
33 
34  // Copy bin contents (ROOT bins start at 1, not 0)
35  for (int i = 0; i < nbins; ++i) {
36  contents_ptr[i] = hist->GetBinContent(i + 1);
37  }
38 
39  // Copy bin edges
40  for (int i = 0; i <= nbins; ++i) {
41  edges_ptr[i] = hist->GetBinLowEdge(i + 1);
42  }
43  // Add the upper edge of the last bin
44  edges_ptr[nbins] = hist->GetBinLowEdge(nbins + 1) + hist->GetBinWidth(nbins + 1);
45 
46  return std::make_tuple(contents, edges);
47 }
double float_t
Definition: Core.h:37

◆ TH2ToNumpy()

std::tuple<py::array_t<M3::float_t>, py::array_t<M3::float_t>, py::array_t<M3::float_t> > TH2ToNumpy ( const TH2 *  hist)

Definition at line 50 of file samples.h.

50  {
51  if (!hist) {
52  throw std::runtime_error("Histogram pointer is null");
53  }
54 
55  int nbinsX = hist->GetNbinsX();
56  int nbinsY = hist->GetNbinsY();
57 
58  // Create 2D numpy array for bin contents (shape: nbinsY x nbinsX to match numpy convention)
59  py::array_t<M3::float_t> contents({nbinsY, nbinsX});
60  auto contents_buf = contents.request();
61  M3::float_t* contents_ptr = static_cast<M3::float_t*>(contents_buf.ptr);
62 
63  // Create numpy arrays for bin edges
64  py::array_t<M3::float_t> edgesX(nbinsX + 1);
65  auto edgesX_buf = edgesX.request();
66  M3::float_t* edgesX_ptr = static_cast<M3::float_t*>(edgesX_buf.ptr);
67 
68  py::array_t<M3::float_t> edgesY(nbinsY + 1);
69  auto edgesY_buf = edgesY.request();
70  M3::float_t* edgesY_ptr = static_cast<M3::float_t*>(edgesY_buf.ptr);
71 
72  // Copy bin contents (ROOT bins start at 1, not 0)
73  // Note: numpy uses row-major order (C-style), so we iterate Y then X
74  for (int iy = 0; iy < nbinsY; ++iy) {
75  for (int ix = 0; ix < nbinsX; ++ix) {
76  contents_ptr[iy * nbinsX + ix] = hist->GetBinContent(ix + 1, iy + 1);
77  }
78  }
79 
80  // Copy X bin edges
81  for (int i = 0; i <= nbinsX; ++i) {
82  edgesX_ptr[i] = hist->GetXaxis()->GetBinLowEdge(i + 1);
83  }
84  edgesX_ptr[nbinsX] = hist->GetXaxis()->GetBinLowEdge(nbinsX + 1) +
85  hist->GetXaxis()->GetBinWidth(nbinsX + 1);
86 
87  // Copy Y bin edges
88  for (int i = 0; i <= nbinsY; ++i) {
89  edgesY_ptr[i] = hist->GetYaxis()->GetBinLowEdge(i + 1);
90  }
91  edgesY_ptr[nbinsY] = hist->GetYaxis()->GetBinLowEdge(nbinsY + 1) +
92  hist->GetYaxis()->GetBinWidth(nbinsY + 1);
93 
94  return std::make_tuple(contents, edgesX, edgesY);
95 }