|
|
|||
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 */
| [ Source navigation ] | [ Diff markup ] | [ Identifier search ] | [ general search ] |
|
This page was automatically generated by the 2.3.7 LXR engine. The LXR team |
|