Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-02-21 09:58:12

0001 // SPDX-License-Identifier: MIT
0002 // Copyright 2018 Moritz Kiehn
0003 //
0004 // Permission is hereby granted, free of charge, to any person obtaining a copy
0005 // of this software and associated documentation files (the "Software"), to deal
0006 // in the Software without restriction, including without limitation the rights
0007 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
0008 // copies of the Software, and to permit persons to whom the Software is
0009 // furnished to do so, subject to the following conditions:
0010 //
0011 // The above copyright notice and this permission notice shall be included in
0012 // all copies or substantial portions of the Software.
0013 //
0014 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
0015 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
0016 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
0017 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
0018 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
0019 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
0020 // SOFTWARE.
0021 
0022 /// \file
0023 /// \brief   N-dimensional histograms with composable axes
0024 /// \author  Moritz Kiehn <msmk@cern.ch>
0025 /// \date    2018-05-22
0026 
0027 #pragma once
0028 
0029 #include <algorithm>
0030 #include <array>
0031 #include <functional>
0032 #include <initializer_list>
0033 #include <numeric>
0034 #include <stdexcept>
0035 #include <type_traits>
0036 #include <utility>
0037 #include <vector>
0038 
0039 namespace dfe {
0040 namespace histogram_impl {
0041 namespace {
0042 
0043 /// A simple n-dimensional array.
0044 ///
0045 /// Data must be accessed through n-dimensional indices. The internal
0046 /// storage format is considered an implementation detail. The size along
0047 /// each dimension is set at run-time.
0048 template<typename T, std::size_t NDimensions>
0049 class NArray {
0050 public:
0051   using Index = std::array<std::size_t, NDimensions>;
0052 
0053   /// Construct default-initialized NArray with given size along each dimension.
0054   NArray(Index size, const T& value = T());
0055   NArray(const NArray&) = default;
0056   NArray(NArray&&) = default;
0057   NArray& operator=(const NArray&) = default;
0058   NArray& operator=(NArray&&) = default;
0059   ~NArray() = default;
0060 
0061   /// Size along all dimensions.
0062   constexpr const Index& size() const { return m_size; }
0063   /// Read-only access element without boundary check.
0064   constexpr const T& operator[](Index idx) const { return m_data[linear(idx)]; }
0065   /// Access element without boundary check.
0066   constexpr T& operator[](Index idx) { return m_data[linear(idx)]; }
0067   /// Read-only access element with boundary check.
0068   const T& at(Index idx) const;
0069   /// Access element with boundary check.
0070   T& at(Index idx);
0071 
0072 private:
0073   constexpr std::size_t linear(Index idx) const;
0074   constexpr bool within_bounds(Index idx) const;
0075 
0076   Index m_size;
0077   std::vector<T> m_data;
0078 };
0079 
0080 } // namespace
0081 } // namespace histogram_impl
0082 
0083 /// Uniform binning without under/overflow bins.
0084 template<typename T>
0085 class UniformAxis {
0086 public:
0087   using Value = T;
0088 
0089   /// \param lower Lower inclusive boundary
0090   /// \param upper Upper exclusive boundary
0091   /// \param nbins Number of data bins within those boundaries
0092   UniformAxis(T lower, T upper, std::size_t nbins);
0093 
0094   /// Total number of bins along this axis including under/overflow bins.
0095   constexpr std::size_t nbins() const { return m_nbins; }
0096   /// Compute bin number for a test value.
0097   std::size_t index(T value) const;
0098 
0099 private:
0100   std::size_t m_nbins;
0101   T m_lower;
0102   T m_upper;
0103 };
0104 
0105 /// Uniform binning with under/overflow bins.
0106 ///
0107 /// The first and last bin index correspond to the under/overflow bin.
0108 template<typename T>
0109 class OverflowAxis {
0110 public:
0111   using Value = T;
0112 
0113   /// \param lower Lower inclusive boundary
0114   /// \param upper Upper exclusive boundary
0115   /// \param nbins Number of data bins within those boundaries
0116   OverflowAxis(T lower, T upper, std::size_t nbins);
0117 
0118   /// Total number of bins along this axis including under/overflow bins.
0119   constexpr std::size_t nbins() const { return 2 + m_ndatabins; }
0120   /// Compute bin number for a test value.
0121   constexpr std::size_t index(T value) const;
0122 
0123 private:
0124   std::size_t m_ndatabins;
0125   T m_lower;
0126   T m_upper;
0127 };
0128 
0129 /// Variable binninng defined by arbitrary bin edges.
0130 template<typename T>
0131 class VariableAxis {
0132 public:
0133   using Value = T;
0134 
0135   /// \param edges Bin edges, lower ones inclusive, upper ones exclusive.
0136   explicit VariableAxis(std::vector<T>&& edges);
0137   /// \param edges Bin edges, lower ones inclusive, upper ones exclusive.
0138   VariableAxis(std::initializer_list<T> edges);
0139 
0140   /// Total number of bins along this axis including under/overflow bins.
0141   constexpr std::size_t nbins() const { return m_edges.size() - 1; }
0142   /// Compute bin number for a test value.
0143   std::size_t index(T value) const;
0144 
0145 private:
0146   std::vector<T> m_edges;
0147 };
0148 
0149 /// A generic histogram with configurable axes.
0150 ///
0151 /// \tparam T    The type of the data stored per bin
0152 /// \tparam Axes Types must provide `::Value`, `.nbins()` and `.index(...)`
0153 template<typename T, typename... Axes>
0154 class Histogram {
0155 public:
0156   using Data = dfe::histogram_impl::NArray<T, sizeof...(Axes)>;
0157   using Index = typename Data::Index;
0158 
0159   Histogram(Axes&&... axes);
0160 
0161   /// Get the number of bins along all axes.
0162   constexpr const Index& size() const { return m_data.size(); }
0163   /// Get the current entry value in the given bin.
0164   const T& value(Index idx) const { return m_data.at(idx); }
0165   /// Fill an entry into the histogram.
0166   ///
0167   /// \param values
0168   /// \param weight Associated weight, the default of 1 just counts entries.
0169   void fill(typename Axes::Value... values, T weight = static_cast<T>(1)) {
0170     // TODO 2018-11-28 how to typedef parameter pack Axes::Value...?
0171     m_data[index(std::index_sequence_for<Axes...>(), values...)] += weight;
0172   }
0173 
0174 private:
0175   template<std::size_t... Is>
0176   constexpr Index index(
0177     std::index_sequence<Is...>, typename Axes::Value... values) const {
0178     return Index{std::get<Is>(m_axes).index(values)...};
0179   }
0180 
0181   Data m_data;
0182   std::tuple<Axes...> m_axes;
0183 };
0184 
0185 // predefined histogram types
0186 
0187 using Histogram1 = Histogram<double, OverflowAxis<double>>;
0188 using Histogram2 =
0189   Histogram<double, OverflowAxis<double>, OverflowAxis<double>>;
0190 
0191 // implementation NArray
0192 
0193 template<typename T, std::size_t NDimensions>
0194 inline histogram_impl::NArray<T, NDimensions>::NArray(
0195   Index size, const T& value)
0196   : m_size(size)
0197   , m_data(
0198       std::accumulate(
0199         size.begin(), size.end(), static_cast<std::size_t>(1),
0200         std::multiplies<std::size_t>()),
0201       value) {}
0202 
0203 // construct linear column-major index from n-dimensional index
0204 template<typename T, std::size_t NDimensions>
0205 constexpr std::size_t
0206 histogram_impl::NArray<T, NDimensions>::linear(Index idx) const {
0207   std::size_t result = 0;
0208   std::size_t step = 1;
0209   for (std::size_t i = 0; i < NDimensions; ++i) {
0210     result += step * idx[i];
0211     step *= m_size[i];
0212   }
0213   return result;
0214 }
0215 
0216 template<typename T, std::size_t NDimensions>
0217 constexpr bool
0218 histogram_impl::NArray<T, NDimensions>::within_bounds(Index idx) const {
0219   for (std::size_t i = 0; i < NDimensions; ++i) {
0220     if (m_size[i] <= idx[i]) {
0221       return false;
0222     }
0223   }
0224   return true;
0225 }
0226 
0227 template<typename T, std::size_t NDimensions>
0228 inline const T&
0229 histogram_impl::NArray<T, NDimensions>::at(Index idx) const {
0230   if (!within_bounds(idx)) {
0231     throw std::out_of_range("NArray index is out of valid range");
0232   }
0233   return m_data[linear(idx)];
0234 }
0235 
0236 template<typename T, std::size_t NDimensions>
0237 inline T&
0238 histogram_impl::NArray<T, NDimensions>::at(Index idx) {
0239   if (!within_bounds(idx)) {
0240     throw std::out_of_range("NArray index is out of valid range");
0241   }
0242   return m_data[linear(idx)];
0243 }
0244 
0245 // implementation UniformAxis
0246 
0247 template<typename T>
0248 inline UniformAxis<T>::UniformAxis(T lower, T upper, std::size_t nbins)
0249   : m_nbins(nbins), m_lower(lower), m_upper(upper) {}
0250 
0251 template<typename T>
0252 inline std::size_t
0253 UniformAxis<T>::index(T value) const {
0254   if (value < this->m_lower) {
0255     throw std::out_of_range("Value is smaller than lower axis limit");
0256   }
0257   if (m_upper <= value) {
0258     throw std::out_of_range("Value is equal or larger than upper axis limit");
0259   }
0260   // cast truncates to integer part; should work since index is always > 0.
0261   return static_cast<std::size_t>(
0262     m_nbins * (value - m_lower) / (m_upper - m_lower));
0263 }
0264 
0265 // implementation OverflowAxis
0266 
0267 template<typename T>
0268 inline OverflowAxis<T>::OverflowAxis(
0269   Value lower, Value upper, std::size_t nbins)
0270   : m_ndatabins(nbins), m_lower(lower), m_upper(upper) {}
0271 
0272 template<typename T>
0273 constexpr std::size_t
0274 OverflowAxis<T>::index(T value) const {
0275   if (value < m_lower) {
0276     return 0;
0277   }
0278   if (m_upper <= value) {
0279     return m_ndatabins + 1;
0280   }
0281   // cast truncates to integer part; should work since index is always > 0.
0282   return 1
0283          + static_cast<std::size_t>(
0284            m_ndatabins * (value - m_lower) / (m_upper - m_lower));
0285 }
0286 
0287 // implementation VariableAxis
0288 
0289 template<typename T>
0290 inline VariableAxis<T>::VariableAxis(std::vector<Value>&& edges)
0291   : m_edges(std::move(edges)) {
0292   if (m_edges.size() < 2) {
0293     throw std::invalid_argument("Less than two bin edges");
0294   }
0295   // edges must be sorted and unique
0296   if (!std::is_sorted(
0297         m_edges.begin(), m_edges.end(), std::less_equal<Value>())) {
0298     throw std::invalid_argument("Bin edges are not sorted or have duplicates");
0299   }
0300 }
0301 
0302 template<typename T>
0303 inline VariableAxis<T>::VariableAxis(std::initializer_list<Value> edges)
0304   : VariableAxis(std::vector<Value>(edges)) {}
0305 
0306 template<typename T>
0307 inline std::size_t
0308 VariableAxis<T>::index(T value) const {
0309   // find upper edge of the corresponding bin
0310   auto it = std::upper_bound(m_edges.begin(), m_edges.end(), value);
0311   if (it == m_edges.begin()) {
0312     throw std::out_of_range("Value is smaller than lower axis limit");
0313   }
0314   if (it == m_edges.end()) {
0315     throw std::out_of_range("Value is equal or larger than upper axis limit");
0316   }
0317   return std::distance(m_edges.begin(), it) - 1;
0318 }
0319 
0320 // implementation Histogram
0321 
0322 template<typename T, typename... Axes>
0323 inline Histogram<T, Axes...>::Histogram(Axes&&... axes)
0324   // access nbins *before* moving the axes, otherwise the axes are invalid.
0325   : m_data(Index{axes.nbins()...}, static_cast<T>(0))
0326   , m_axes(std::move(axes)...) {}
0327 
0328 } // namespace dfe