/// \file ROOT/RHist.hxx /// \ingroup HistV7 /// \author Axel Naumann /// \date 2015-03-23 /// \warning This is part of the ROOT 7 prototype! It will change without notice. It might trigger earthquakes. Feedback /// is welcome! /************************************************************************* * Copyright (C) 1995-2015, Rene Brun and Fons Rademakers. * * All rights reserved. * * * * For the licensing terms see $ROOTSYS/LICENSE. * * For the list of contributors see $ROOTSYS/README/CREDITS. * *************************************************************************/ #ifndef ROOT7_RHist #define ROOT7_RHist #include "ROOT/RSpan.hxx" #include "ROOT/RAxis.hxx" #include "ROOT/RHistBinIter.hxx" #include "ROOT/RHistImpl.hxx" #include "ROOT/RHistData.hxx" #include "ROOT/RLogger.hxx" #include #include namespace ROOT { namespace Experimental { // fwd declare for fwd declare for friend declaration in RHist... template class... STAT> class RHist; // fwd declare for friend declaration in RHist. template class... STAT> class RHist HistFromImpl(std::unique_ptr::ImplBase_t> pHistImpl); /** \class RHist Histogram class for histograms with `DIMENSIONS` dimensions, where each bin count is stored by a value of type `PRECISION`. STAT stores statistical data of the entries filled into the histogram (bin content, uncertainties etc). A histogram counts occurrences of values or n-dimensional combinations thereof. Contrary to for instance a `RTree`, a histogram combines adjacent values. The resolution of this combination is defined by the axis binning, see e.g. http://www.wikiwand.com/en/Histogram */ template class... STAT> class RHist { public: /// The type of the `Detail::RHistImplBase` of this histogram. using ImplBase_t = Detail::RHistImplBase, STAT...>>; /// The coordinates type: a `DIMENSIONS`-dimensional `std::array` of `double`. using CoordArray_t = typename ImplBase_t::CoordArray_t; /// The type of weights using Weight_t = PRECISION; /// Pointer type to `HistImpl_t::Fill`, for faster access. using FillFunc_t = typename ImplBase_t::FillFunc_t; /// Range. using AxisRange_t = typename ImplBase_t::AxisIterRange_t; using const_iterator = Detail::RHistBinIter; /// Number of dimensions of the coordinates static constexpr int GetNDim() noexcept { return DIMENSIONS; } RHist() = default; RHist(RHist &&) = default; RHist(const RHist &other): fImpl(other.fImpl->Clone()), fFillFunc(other.fFillFunc) {} /// Create a histogram from an `array` of axes (`RAxisConfig`s). Example code: /// /// Construct a 1-dimensional histogram that can be filled with `floats`s. /// The axis has 10 bins between 0. and 1. The two outermost sets of curly /// braces are to reach the initialization of the `std::array` elements; the /// inner one is for the initialization of a `RAxisCoordinate`. /// /// RHist<1,float> h1f({{ {10, 0., 1.} }}); /// /// Construct a 2-dimensional histogram, with the first axis as before, and /// the second axis having non-uniform ("irregular") binning, where all bin- /// edges are specified. As this is itself an array it must be enclosed by /// double curlies. /// /// RHist<2,int> h2i({{ {10, 0., 1.}, {{-1., 0., 1., 10., 100.}} }}); explicit RHist(std::array axes); /// Constructor overload taking the histogram title. RHist(std::string_view histTitle, std::array axes); /// Constructor overload that's only available for a 1-dimensional histogram. template ::type> explicit RHist(const RAxisConfig &xaxis): RHist(std::array{{xaxis}}) {} /// Constructor overload that's only available for a 1-dimensional histogram, /// also passing the histogram title. template ::type> RHist(std::string_view histTitle, const RAxisConfig &xaxis): RHist(histTitle, std::array{{xaxis}}) {} /// Constructor overload that's only available for a 2-dimensional histogram. template ::type> RHist(const RAxisConfig &xaxis, const RAxisConfig &yaxis): RHist(std::array{{xaxis, yaxis}}) {} /// Constructor overload that's only available for a 2-dimensional histogram, /// also passing the histogram title. template ::type> RHist(std::string_view histTitle, const RAxisConfig &xaxis, const RAxisConfig &yaxis) : RHist(histTitle, std::array{{xaxis, yaxis}}) {} /// Constructor overload that's only available for a 3-dimensional histogram. template ::type> RHist(const RAxisConfig &xaxis, const RAxisConfig &yaxis, const RAxisConfig &zaxis) : RHist(std::array{{xaxis, yaxis, zaxis}}) {} /// Constructor overload that's only available for a 3-dimensional histogram, /// also passing the histogram title. template ::type> RHist(std::string_view histTitle, const RAxisConfig &xaxis, const RAxisConfig &yaxis, const RAxisConfig &zaxis) : RHist(histTitle, std::array{{xaxis, yaxis, zaxis}}) {} /// Access the ImplBase_t this RHist points to. ImplBase_t *GetImpl() const noexcept { return fImpl.get(); } /// "Steal" the ImplBase_t this RHist points to. std::unique_ptr TakeImpl() && noexcept { return std::move(fImpl); } /// Add `weight` to the bin containing coordinate `x`. void Fill(const CoordArray_t &x, Weight_t weight = (Weight_t)1) noexcept { (fImpl.get()->*fFillFunc)(x, weight); } /// For each coordinate in `xN`, add `weightN[i]` to the bin at coordinate /// `xN[i]`. The sizes of `xN` and `weightN` must be the same. This is more /// efficient than many separate calls to `Fill()`. void FillN(const std::span xN, const std::span weightN) noexcept { fImpl->FillN(xN, weightN); } /// For each coordinate in `xN`, add `weightN[i]` to the bin at coordinate /// `xN[i]`. The sizes of `xN` and `weightN` must be the same. This is more /// efficient than many separate calls to `Fill()`. /// Overload for passing initializer lists. void FillN(std::initializer_list xN, std::initializer_list weightN) noexcept { fImpl->FillN(std::span(xN.begin(), xN.end()), std::span(weightN.begin(), weightN.end())); } /// Convenience overload: `FillN()` with weight 1. void FillN(const std::span xN) noexcept { fImpl->FillN(xN); } /// Convenience overload: `FillN()` with weight 1. /// Overload for passing initializer lists. void FillN(std::initializer_list xN) noexcept { fImpl->FillN(std::span(xN.begin(), xN.end())); } /// Get the number of entries this histogram was filled with. int64_t GetEntries() const noexcept { return fImpl->GetStat().GetEntries(); } /// Get the content of the bin at `x`. Weight_t GetBinContent(const CoordArray_t &x) const { return fImpl->GetBinContent(x); } /// Get the uncertainty on the content of the bin at `x`. double GetBinUncertainty(const CoordArray_t &x) const { return fImpl->GetBinUncertainty(x); } const_iterator begin() const { return const_iterator(*fImpl, 1); } const_iterator end() const { return const_iterator(*fImpl, fImpl->GetNBinsNoOver() + 1); } /// Swap *this and other. /// /// Very efficient; swaps the `fImpl` pointers. void swap(RHist &other) noexcept { std::swap(fImpl, other.fImpl); std::swap(fFillFunc, other.fFillFunc); } private: /// The actual histogram implementation. std::unique_ptr fImpl; /// Pointer to RHistImpl::Fill() member function. FillFunc_t fFillFunc = nullptr; //! friend RHist HistFromImpl<>(std::unique_ptr); }; /// RHist with no STAT parameter uses RHistStatContent by default. template class RHist: public RHist { using RHist::RHist; }; /// Swap two histograms. /// /// Very efficient; swaps the `fImpl` pointers. template class... STAT> void swap(RHist &a, RHist &b) noexcept { a.swap(b); } namespace Internal { /** Generate RHist::fImpl from RHist constructor arguments. */ template struct RHistImplGen { /// Select the template argument for the next axis type, and "recurse" into /// RHistImplGen for the next axis. template std::unique_ptr> MakeNextAxis(std::string_view title, const std::array &axes, PROCESSEDAXISCONFIG... processedAxisArgs) { using NextAxis_t = typename AxisConfigToType::Axis_t; NextAxis_t nextAxis = AxisConfigToType()(axes[IDIM]); using HistImpl_t = RHistImplGen; return HistImpl_t()(title, axes, processedAxisArgs..., nextAxis); } /// Make a RHistImpl-derived object reflecting the RAxisConfig array. /// /// Delegate to the appropriate MakeNextAxis instantiation, depending on the /// axis type selected in the RAxisConfig. /// \param title - title of the derived object. /// \param axes - `RAxisConfig` objects describing the axis of the resulting /// RHistImpl. /// \param processedAxisArgs - the RAxisBase-derived axis objects describing the /// axes of the resulting RHistImpl. There are `IDIM` of those; in the end /// (`IDIM` == `GetNDim()`), all `axes` have been converted to /// `processedAxisArgs` and the RHistImpl constructor can be invoked, passing /// the `processedAxisArgs`. std::unique_ptr> operator()(std::string_view title, const std::array &axes, PROCESSEDAXISCONFIG... processedAxisArgs) { switch (axes[IDIM].GetKind()) { case RAxisConfig::kEquidistant: return MakeNextAxis(title, axes, processedAxisArgs...); case RAxisConfig::kGrow: return MakeNextAxis(title, axes, processedAxisArgs...); case RAxisConfig::kIrregular: return MakeNextAxis(title, axes, processedAxisArgs...); default: R__LOG_ERROR(HistLog()) << "Unhandled axis kind"; } return nullptr; } }; /// Generate RHist::fImpl from constructor arguments; recursion end. template /// Create the histogram, now that all axis types and initializer objects are /// determined. struct RHistImplGen { using HistImplBase_t = ROOT::Experimental::Detail::RHistImplBase; std::unique_ptr operator()(std::string_view title, const std::array &, PROCESSEDAXISCONFIG... axisArgs) { using HistImplt_t = Detail::RHistImpl; return std::make_unique(title, axisArgs...); } }; } // namespace Internal template class... STAT> RHist::RHist(std::string_view title, std::array axes) : fImpl{std::move( Internal::RHistImplGen, STAT...>>()( title, axes))} { fFillFunc = fImpl->GetFillFunc(); } template class... STAT> RHist::RHist(std::array axes): RHist("", axes) {} /// Adopt an external, stand-alone RHistImpl. The RHist will take ownership. template class... STAT> RHist HistFromImpl(std::unique_ptr::ImplBase_t> pHistImpl) { RHist ret; ret.fFillFunc = pHistImpl->GetFillFunc(); std::swap(ret.fImpl, pHistImpl); return ret; } /// \name RHist Typedefs ///\{ Convenience typedefs (ROOT6-compatible type names) // Keep them as typedefs, to make sure old-style documentation tools can understand them. using RH1D = RHist<1, double, RHistStatContent, RHistStatUncertainty>; using RH1F = RHist<1, float, RHistStatContent, RHistStatUncertainty>; using RH1C = RHist<1, char, RHistStatContent>; using RH1I = RHist<1, int, RHistStatContent>; using RH1LL = RHist<1, int64_t, RHistStatContent>; using RH2D = RHist<2, double, RHistStatContent, RHistStatUncertainty>; using RH2F = RHist<2, float, RHistStatContent, RHistStatUncertainty>; using RH2C = RHist<2, char, RHistStatContent>; using RH2I = RHist<2, int, RHistStatContent>; using RH2LL = RHist<2, int64_t, RHistStatContent>; using RH3D = RHist<3, double, RHistStatContent, RHistStatUncertainty>; using RH3F = RHist<3, float, RHistStatContent, RHistStatUncertainty>; using RH3C = RHist<3, char, RHistStatContent>; using RH3I = RHist<3, int, RHistStatContent>; using RH3LL = RHist<3, int64_t, RHistStatContent>; ///\} /// Add two histograms. /// /// This operation may currently only be performed if the two histograms have /// the same axis configuration, use the same precision, and if `from` records /// at least the same statistics as `to` (recording more stats is fine). /// /// Adding histograms with incompatible axis binning will be reported at runtime /// with an `std::runtime_error`. Insufficient statistics in the source /// histogram will be detected at compile-time and result in a compiler error. /// /// In the future, we may either adopt a more relaxed definition of histogram /// addition or provide a mechanism to convert from one histogram type to /// another. We currently favor the latter path. template class... STAT_TO, template class... STAT_FROM> void Add(RHist &to, const RHist &from) { // Enforce "same axis configuration" policy. auto& toImpl = *to.GetImpl(); const auto& fromImpl = *from.GetImpl(); for (int dim = 0; dim < DIMENSIONS; ++dim) { if (!toImpl.GetAxis(dim).HasSameBinningAs(fromImpl.GetAxis(dim))) { throw std::runtime_error("Attempted to add RHists with incompatible axis binning"); } } // Now that we know that the two axes have the same binning, we can just add // the statistics directly. toImpl.GetStat().Add(fromImpl.GetStat()); } } // namespace Experimental } // namespace ROOT #endif