Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-10-19 10:00:52

0001 // @(#)root/mathcore:$Id$
0002 // Author: L. Moneta Tue Nov 14 15:44:38 2006
0003 
0004 /**********************************************************************
0005  *                                                                    *
0006  * Copyright (c) 2006  LCG ROOT Math Team, CERN/PH-SFT                *
0007  *                                                                    *
0008  *                                                                    *
0009  **********************************************************************/
0010 
0011 // Utility functions for all ROOT Math classes
0012 
0013 #ifndef ROOT_Math_Util
0014 #define ROOT_Math_Util
0015 
0016 #include <algorithm>
0017 #include <chrono>
0018 #include <cmath>
0019 #include <functional>
0020 #include <limits>
0021 #include <numeric>
0022 #include <sstream>
0023 #include <string>
0024 
0025 // This can be protected against by defining ROOT_Math_VecTypes
0026 // This is only used for the R__HAS_STD_EXPERIMENTAL_SIMD define
0027 // and a single VecCore function in EvalLog
0028 #ifndef ROOT_Math_VecTypes
0029 #include "Types.h"
0030 #endif
0031 
0032 
0033 // for defining unused variables in the interfaces
0034 //  and have still them in the documentation
0035 #define MATH_UNUSED(var)   (void)var
0036 
0037 
0038 namespace ROOT {
0039 
0040 namespace Math {
0041 
0042 /**
0043    namespace defining Utility functions needed by mathcore
0044 */
0045 namespace Util {
0046 
0047 class TimingScope {
0048 
0049 public:
0050    TimingScope(std::function<void(std::string const&)> printer, std::string const &message);
0051 
0052    ~TimingScope();
0053 
0054 private:
0055    std::chrono::steady_clock::time_point fBegin;
0056    std::function<void(std::string const&)> fPrinter;
0057    const std::string fMessage;
0058 };
0059 
0060    /**
0061       Utility function for conversion to strings
0062    */
0063    template <class T>
0064    std::string ToString(const T &val)
0065    {
0066       std::ostringstream buf;
0067       buf << val;
0068 
0069       std::string ret = buf.str();
0070       return ret;
0071    }
0072 
0073    /// safe evaluation of log(x) with a protections against negative or zero argument to the log
0074    /// smooth linear extrapolation below function values smaller than  epsilon
0075    /// (better than a simple cut-off)
0076 
0077    template<class T>
0078    inline T EvalLog(T x) {
0079       static const T epsilon = T(2.0 * std::numeric_limits<double>::min());
0080 
0081       if constexpr (std::is_arithmetic_v<T>) {
0082          return x <= epsilon ? x / epsilon + std::log(epsilon) - T(1.0) : std::log(x);
0083       } else {
0084          // assume std::simd for non-arithmetic types
0085          T logval{};
0086          auto mask = x <= epsilon;
0087          where(mask, logval) = x / epsilon + log(epsilon) - T(1.0);
0088          where(!mask, logval) = log(x);
0089          return logval;
0090       }
0091    }
0092 
0093    } // end namespace Util
0094 
0095    /// \class KahanSum
0096    /// The Kahan summation is a compensated summation algorithm, which significantly reduces numerical errors
0097    /// when adding a sequence of finite-precision floating point numbers.
0098    /// This is done by keeping a separate running compensation (a variable to accumulate small errors).
0099    ///
0100    /// ### Auto-vectorisable accumulation
0101    /// This class can internally use multiple accumulators (template parameter `N`).
0102    /// When filled from a collection that supports index access from a *contiguous* block of memory,
0103    /// compilers such as gcc, clang and icc can auto-vectorise the accumulation. This happens by cycling
0104    /// through the internal accumulators based on the value of "`index % N`", so `N` accumulators can be filled from a block
0105    /// of `N` numbers in a single instruction.
0106    ///
0107    /// The usage of multiple accumulators might slightly increase the precision in comparison to the single-accumulator version
0108    /// with `N = 1`.
0109    /// This depends on the order and magnitude of the numbers being accumulated. Therefore, in rare cases, the accumulation
0110    /// result can change *in dependence of N*, even when the data are identical.
0111    /// The magnitude of such differences is well below the precision of the floating point type, and will therefore mostly show
0112    /// in the compensation sum(see Carry()). Increasing the number of accumulators therefore only makes sense to
0113    /// speed up the accumulation, but not to increase precision.
0114    ///
0115    /// \param T The type of the values to be accumulated.
0116    /// \param N Number of accumulators. Defaults to 1. Ideal values are the widths of a vector register on the relevant architecture.
0117    /// Depending on the instruction set, good values are:
0118    /// - AVX2-float: 8
0119    /// - AVX2-double: 4
0120    /// - AVX512-float: 16
0121    /// - AVX512-double: 8
0122    ///
0123    /// ### Examples
0124    ///
0125    /// ~~~{.cpp}
0126    /// std::vector<double> numbers(1000);
0127    /// for (std::size_t i=0; i<1000; ++i) {
0128    ///    numbers[i] = rand();
0129    /// }
0130    ///
0131    /// ROOT::Math::KahanSum<double, 4> k;
0132    /// k.Add(numbers.begin(), numbers.end());
0133    /// // or
0134    /// k.Add(numbers);
0135    /// ~~~
0136    /// ~~~{.cpp}
0137    /// double offset = 10.;
0138    /// auto result = ROOT::Math::KahanSum<double, 4>::Accumulate(numbers.begin(), numbers.end(), offset);
0139    /// ~~~
0140    template<typename T = double, unsigned int N = 1>
0141    class KahanSum {
0142      public:
0143        /// Initialise the sum.
0144        /// \param[in] initialValue Initialise with this value. Defaults to 0.
0145        explicit KahanSum(T initialValue = T{}) {
0146          fSum[0] = initialValue;
0147          std::fill(std::begin(fSum)+1, std::end(fSum), 0.);
0148          std::fill(std::begin(fCarry), std::end(fCarry), 0.);
0149        }
0150 
0151        /// Initialise with a sum value and a carry value.
0152        /// \param[in] initialSumValue Initialise the sum with this value.
0153        /// \param[in] initialCarryValue Initialise the carry with this value.
0154        KahanSum(T initialSumValue, T initialCarryValue) {
0155           fSum[0] = initialSumValue;
0156           fCarry[0] = initialCarryValue;
0157           std::fill(std::begin(fSum)+1, std::end(fSum), 0.);
0158           std::fill(std::begin(fCarry)+1, std::end(fCarry), 0.);
0159        }
0160 
0161        /// Initialise the sum with a pre-existing state.
0162        /// \param[in] sumBegin Begin of iterator range with values to initialise the sum with.
0163        /// \param[in] sumEnd End of iterator range with values to initialise the sum with.
0164        /// \param[in] carryBegin Begin of iterator range with values to initialise the carry with.
0165        /// \param[in] carryEnd End of iterator range with values to initialise the carry with.
0166        template<class Iterator>
0167        KahanSum(Iterator sumBegin, Iterator sumEnd, Iterator carryBegin, Iterator carryEnd) {
0168           assert(std::distance(sumBegin, sumEnd) == N);
0169           assert(std::distance(carryBegin, carryEnd) == N);
0170           std::copy(sumBegin, sumEnd, std::begin(fSum));
0171           std::copy(carryBegin, carryEnd, std::begin(fCarry));
0172        }
0173 
0174        /// Constructor to create a KahanSum from another KahanSum with a different number of accumulators
0175        template <unsigned int M>
0176        KahanSum(KahanSum<T,M> const& other) {
0177          fSum[0] = other.Sum();
0178          fCarry[0] = other.Carry();
0179          std::fill(std::begin(fSum)+1, std::end(fSum), 0.);
0180          std::fill(std::begin(fCarry)+1, std::end(fCarry), 0.);
0181        }
0182 
0183        /// Single-element accumulation. Will not vectorise.
0184        void Add(T x) {
0185           auto y = x - fCarry[0];
0186           auto t = fSum[0] + y;
0187           fCarry[0] = (t - fSum[0]) - y;
0188           fSum[0] = t;
0189        }
0190 
0191 
0192        /// Accumulate from a range denoted by iterators.
0193        ///
0194        /// This function will auto-vectorise with random-access iterators.
0195        /// \param[in] begin Beginning of a range. Needs to be a random access iterator for automatic
0196        /// vectorisation, because a contiguous block of memory needs to be read.
0197        /// \param[in] end   End of the range.
0198        template <class Iterator>
0199        void Add(Iterator begin, Iterator end) {
0200            static_assert(std::is_floating_point<
0201                typename std::remove_reference<decltype(*begin)>::type>::value,
0202                "Iterator needs to point to floating-point values.");
0203            const std::size_t n = std::distance(begin, end);
0204 
0205            for (std::size_t i=0; i<n; ++i) {
0206              AddIndexed(*(begin++), i);
0207            }
0208        }
0209 
0210 
0211        /// Fill from a container that supports index access.
0212        /// \param[in] inputs Container with index access such as std::vector or array.
0213        template<class Container_t>
0214        void Add(const Container_t& inputs) {
0215          static_assert(std::is_floating_point<typename Container_t::value_type>::value,
0216              "Container does not hold floating-point values.");
0217          for (std::size_t i=0; i < inputs.size(); ++i) {
0218            AddIndexed(inputs[i], i);
0219          }
0220        }
0221 
0222 
0223        /// Iterate over a range and return an instance of a KahanSum.
0224        ///
0225        /// See Add(Iterator,Iterator) for details.
0226        /// \param[in] begin Beginning of a range.
0227        /// \param[in] end   End of the range.
0228        /// \param[in] initialValue Optional initial value.
0229        template <class Iterator>
0230        static KahanSum<T, N> Accumulate(Iterator begin, Iterator end,
0231            T initialValue = T{}) {
0232            KahanSum<T, N> theSum(initialValue);
0233            theSum.Add(begin, end);
0234 
0235            return theSum;
0236        }
0237 
0238 
0239        /// Add `input` to the sum.
0240        ///
0241        /// Particularly helpful when filling from a for loop.
0242        /// This function can be inlined and auto-vectorised if
0243        /// the index parameter is used to enumerate *consecutive* fills.
0244        /// Use Add() or Accumulate() when no index is available.
0245        /// \param[in] input Value to accumulate.
0246        /// \param[in] index Index of the value. Determines internal accumulator that this
0247        /// value is added to. Make sure that consecutive fills have consecutive indices
0248        /// to make a loop auto-vectorisable. The actual value of the index does not matter,
0249        /// as long as it is consecutive.
0250        void AddIndexed(T input, std::size_t index) {
0251          const unsigned int i = index % N;
0252          const T y = input - fCarry[i];
0253          const T t = fSum[i] + y;
0254          fCarry[i] = (t - fSum[i]) - y;
0255          fSum[i] = t;
0256        }
0257 
0258        /// \return Compensated sum.
0259        T Sum() const {
0260          return std::accumulate(std::begin(fSum), std::end(fSum), 0.);
0261        }
0262 
0263        /// \return Compensated sum.
0264        T Result() const {
0265          return Sum();
0266        }
0267 
0268        /// \return The sum used for compensation.
0269        T Carry() const {
0270          return std::accumulate(std::begin(fCarry), std::end(fCarry), 0.);
0271        }
0272 
0273        /// Add `arg` into accumulator. Does not vectorise.
0274        KahanSum<T, N>& operator+=(T arg) {
0275          Add(arg);
0276          return *this;
0277        }
0278 
0279        /// Add other KahanSum into accumulator. Does not vectorise.
0280        ///
0281        /// Based on KahanIncrement from:
0282        /// Y. Tian, S. Tatikonda and B. Reinwald, "Scalable and Numerically Stable Descriptive Statistics in SystemML," 2012 IEEE 28th International Conference on Data Engineering, 2012, pp. 1351-1359, doi: 10.1109/ICDE.2012.12.
0283        /// Note that while Tian et al. add the carry in the first step, we subtract
0284        /// the carry, in accordance with the Add(Indexed) implementation(s) above.
0285        /// This is purely an implementation choice that has no impact on performance.
0286        ///
0287        /// \note Take care when using += (and -=) to add other KahanSums into a zero-initialized
0288        ///       KahanSum. The operator behaves correctly in this case, but the result may be slightly
0289        ///       off if you expect 0 + x to yield exactly x (where 0 is the zero-initialized KahanSum
0290        ///       and x another KahanSum). In particular, x's carry term may get lost. This doesn't
0291        ///       just happen with zero-initialized KahanSums; see the SubtractWithABitTooSmallCarry
0292        ///       test case in the testKahan unittest for other examples. This behavior is internally
0293        ///       consistent: the carry also gets lost if you switch the operands and it also happens with
0294        ///       other KahanSum operators.
0295        template<typename U, unsigned int M>
0296        KahanSum<T, N>& operator+=(const KahanSum<U, M>& other) {
0297          U corrected_arg_sum = other.Sum() - (fCarry[0] + other.Carry());
0298          U sum = fSum[0] + corrected_arg_sum;
0299          U correction = (sum - fSum[0]) - corrected_arg_sum;
0300          fSum[0] = sum;
0301          fCarry[0] = correction;
0302          return *this;
0303        }
0304 
0305        /// Subtract other KahanSum. Does not vectorise.
0306        ///
0307        /// Based on KahanIncrement from: Tian et al., 2012 (see operator+= documentation).
0308        template<typename U, unsigned int M>
0309        KahanSum<T, N>& operator-=(KahanSum<U, M> const& other) {
0310          U corrected_arg_sum = -other.Sum() - (fCarry[0] - other.Carry());
0311          U sum = fSum[0] + corrected_arg_sum;
0312          U correction = (sum - fSum[0]) - corrected_arg_sum;
0313          fSum[0] = sum;
0314          fCarry[0] = correction;
0315          return *this;
0316        }
0317 
0318        KahanSum<T, N> operator-()
0319        {
0320           return {-this->fSum[0], -this->fCarry[0]};
0321        }
0322 
0323        template<typename U, unsigned int M>
0324        bool operator ==(KahanSum<U, M> const& other) const {
0325           return (this->Sum() == other.Sum()) && (this->Carry() == other.Carry());
0326        }
0327 
0328        template<typename U, unsigned int M>
0329        bool operator !=(KahanSum<U, M> const& other) const {
0330           return !(*this == other);
0331        }
0332 
0333      private:
0334        T fSum[N];
0335        T fCarry[N];
0336    };
0337 
0338    /// Add two non-vectorized KahanSums.
0339    template<typename T, unsigned int N, typename U, unsigned int M>
0340    KahanSum<T, N> operator+(const KahanSum<T, N>& left, const KahanSum<U, M>& right) {
0341       KahanSum<T, N> sum(left);
0342       sum += right;
0343       return sum;
0344    }
0345 
0346    /// Subtract two non-vectorized KahanSums.
0347    template<typename T, unsigned int N, typename U, unsigned int M>
0348    KahanSum<T, N> operator-(const KahanSum<T, N>& left, const KahanSum<U, M>& right) {
0349       KahanSum<T, N> sum(left);
0350       sum -= right;
0351       return sum;
0352    }
0353 
0354    } // end namespace Math
0355 
0356 } // end namespace ROOT
0357 
0358 
0359 #endif /* ROOT_Math_Util */