All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
FastAndPoorGauss.h
Go to the documentation of this file.
1 /**
2  * @file icarusalg/Utilities/FastAndPoorGauss.h
3  * @brief Fast approximate Gaussian random translator.
4  * @date February 15, 2020
5  */
6 
7 #ifndef ICARUSALG_UTILITIES_FASTANDPOORGAUSS_H
8 #define ICARUSALG_UTILITIES_FASTANDPOORGAUSS_H
9 
10 // ROOT
11 #include "TMath.h" // TMath::ErfInverse()
12 
13 // C/C++ standard library
14 #include <array>
15 #include <type_traits> // std::is_integral_v, std::is_signed_v
16 #include <cmath> // std::sqrt()
17 #include <cstddef> // std::size_t
18 
19 
20 // -----------------------------------------------------------------------------
21 namespace util::details {
22 
23  // ---------------------------------------------------------------------------
24  /// Returns whether the integral type `value` is a power of 2.
25  template <typename T>
26  constexpr bool isPowerOfTwo(T value)
27  {
28  static_assert(std::is_integral_v<T>);
29  if constexpr(std::is_signed_v<T>) if (value < 0) return false;
30  do {
31  if (value & 1) return (value == 1);
32  } while (value >>= 1);
33  return false;
34  } // isPowerOfTwo()
35 
36 
37  // ---------------------------------------------------------------------------
38 
39 
40 } // namespace util::details
41 
42 
43 // -----------------------------------------------------------------------------
44 namespace util {
45  template <std::size_t N, typename T>
47 
48  template <typename T>
50 
51  template <typename T>
53 
54 } // namespace util
55 
56 // -----------------------------------------------------------------------------
57 /**
58  * @brief Translates a number _u_ uniformly distributed between 0 and 1
59  * into a Gaussian distributed one _z_.
60  * @param N number of possible values returned; it *must* be a power of 2
61  * @param T (default: `double`) type of number for _u_ and _z_
62  *
63  * The focus of this algorithm is speed, which is paid with some memory and a
64  * lot of precision.
65  * The algorithm picks one of only `N` precomputed possible values for each
66  * input. The mapping of @f$ u \in [ 0, +1 [ @f$ into a real number is a
67  * multi-step function with `N` steps. The steps are located at @f$ 1/2N @f$
68  * steps through the domain of _u_.
69  *
70  * The returned number _z_ is distributed according to a standard normal
71  * distribution with mean 0 and standard deviation 1. The number can be turned
72  * into one distributed with arbitrary mean @f$ \mu @f$ and standard deviation
73  * @f$ \sigma @f$ with the usual transformation @f$ x = \mu + \sigma z @f$
74  * (see e.g. `util::GaussianTransformer`).
75  *
76  *
77  * Resources
78  * ----------
79  *
80  * Math is internally performed in `T` precision, except for the initialization
81  * that is performed in double precision. The precomputed value table is
82  * `sizeof(T) * N` bytes large.
83  *
84  *
85  * @note This class is tuned for performance, and it allocates its data on the
86  * stack. Stack overflows have been observed in Linux with a size of
87  * `N` 2^20^. Stack overflows are very puzzling since they do not present
88  * any standard diagnostics and debuggers may not notice them.
89  * Bottom line is: do not overdo with the number of samples, or ask the
90  * author to provide a special version using dynamic memory allocation.
91  *
92  */
93 template <std::size_t N, typename T = double>
95  static_assert(
96  util::details::isPowerOfTwo(N), "Template parameter N must be a power of 2."
97  );
98 
99  public:
100  using Data_t = T; ///< Type of data to deal with.
101 
102  static constexpr std::size_t NPoints = N; ///< Number of sampled points.
103 
104  //@{
105  /// Returns the Gaussian distributed value corresponding to `u`.
106  Data_t transform(Data_t const u) const { return fSamples[indexOf(u)]; }
107  Data_t operator() (Data_t const u) const { return transform(u); }
108  //@}
109 
110  private:
111  /// Sampled points of inverse Gaussian.
112  static std::array<Data_t, N> const fSamples;
113 
114  /// Returns the index of the precomputed table serving the value `u`.
115  std::size_t indexOf(Data_t u) const;
116 
117  /// Fills the pre-sampling table.
118  static std::array<Data_t, NPoints> makeSamples();
119 
120 }; // util::FastAndPoorGauss<>
121 
122 
123 
124 // -----------------------------------------------------------------------------
125 /**
126  * @brief Transforms a standard normal number into one on a different normal
127  * distribution.
128  * @tparam T type of the data
129  *
130  * This functor applies the simple mapping of a standard normal variable with
131  * mean 0 and standard deviation 1 into one with arbitrary mean and standard
132  * deviation: @f$ x = \mu + \sigma z @f$.
133  *
134  */
135 template <typename T>
137 
138  public:
139  using Data_t = T; ///< Type of data to deal with.
140 
141  /// Constructor: selects the mean and standard deviation.
142  GaussianTransformer(T mean, T stddev): fMean(mean), fStdDev(stddev) {}
143 
144  //@{
145  /// Transforms normal value `z` into the target distribution.
146  Data_t transform(Data_t const z) const
147  { return transform(z, mean(), stdDev()); }
148  Data_t operator() (Data_t const z) const { return transform(z); }
149  //@}
150 
151  /// Returns the mean of the target Gaussian distribution.
152  Data_t mean() const { return fMean; }
153 
154  /// Returns the standard deviation of the target Gaussian distribution.
155  Data_t stdDev() const { return fStdDev; }
156 
157  /// Transforms normal value `z` into one distributed with `mean` and `stddev`.
158  static Data_t transform
159  (Data_t const z, Data_t const mean, Data_t const stddev)
160  { return mean + z * stddev; }
161 
162  private:
163 
164  Data_t const fMean = Data_t{ 0.0 };
165  Data_t const fStdDev = Data_t{ 1.0 };
166 
167 }; // util::GaussianTransformer<>
168 
169 
170 //------------------------------------------------------------------------------
171 /**
172  * @brief Samples the interval [ 0, 1 ] in sequence, cyclically.
173  * @tparam T (default: `double`) type of number returned
174  *
175  * This object `extract()`s in sequence numbers in the interval [ 0 ; 1 ].
176  * The interval is split in equally sized bins, and each time `extract()`
177  * returns the center of the next bin, starting with the first one.
178  * After reaching the last bin, it restarts the cycle.
179  *
180  * Example of usage:
181  * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp}
182  * std::array<double, 100N> seq1, seq2;
183  *
184  * util::UniformSequence<> extract { 100 };
185  * for (auto& v: seq) v = extract();
186  *
187  * std::generate(seq.begin(), seq.end(), util::UniformSequence<>{ 100 });
188  * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
189  * (the two sequences are identical).
190  */
191 template <typename T = double>
192 class util::UniformSequence {
193  public:
194  using Data_t = T; ///< Type of data required.
195 
196  /// Initializes a sequence of N steps.
197  UniformSequence(unsigned int N)
198  : fN(N), fStep(Data_t{ 1 }/fN), fOffset(fStep/Data_t{ 2 })
199  {}
200 
201  //@{
202  /// Returns the next number in the sequence.
203  Data_t extract() { return fOffset + next() * fStep; }
204  Data_t operator() () { return extract(); }
205  //@}
206 
207  /// Restarts the sequence.
208  void reset() { fNext = 0U; }
209 
210  private:
211  unsigned int const fN; ///< The number of points the interval is split into.
212 
213  Data_t const fStep; ///< Size of each step.
214  Data_t const fOffset; ///< Offset of the values (half step).
215 
216  unsigned int fNext = 0U; ///< The next point to be delivered.
217 
218  /// Returns the current point and prepares for the next one.
219  unsigned int next();
220 
221 }; // class UniformSequence<>
222 
223 
224 // -----------------------------------------------------------------------------
225 // --- Template implementation
226 // -----------------------------------------------------------------------------
227 template <std::size_t N, typename T>
228 std::array<T, N> const util::FastAndPoorGauss<N, T>::fSamples
230 
231 
232 // -----------------------------------------------------------------------------
233 template <std::size_t N, typename T>
235  return static_cast<std::size_t>(u * NPoints);
236 } // util::FastAndPoorGauss<>::indexOf()
237 
238 
239 // -----------------------------------------------------------------------------
240 template <std::size_t N, typename T>
241 auto util::FastAndPoorGauss<N, T>::makeSamples() -> std::array<Data_t, NPoints>
242 {
243 
244  std::array<Data_t, NPoints> samples;
245 
246  double const V2 = std::sqrt(2.0);
247 
248  util::UniformSequence<Data_t> extract { NPoints };
249 
250  for(Data_t& value: samples)
251  value = static_cast<Data_t>(TMath::ErfInverse(extract() * 2.0 - 1.0) * V2);
252 
253  return samples;
254 } // util::FastAndPoorGauss<>::makeSamples()
255 
256 
257 // -----------------------------------------------------------------------------
258 // --- util::UniformSequence
259 // -----------------------------------------------------------------------------
260 template <typename T>
262  unsigned int i = fNext;
263  if (++fNext == fN) reset();
264  return i;
265 } // util::UniformSequence<T>::next()
266 
267 
268 // -----------------------------------------------------------------------------
269 
270 
271 #endif // ICARUSALG_UTILITIES_FASTANDPOORGAUSS_H
T Data_t
Type of data to deal with.
Data_t transform(Data_t const u) const
Returns the Gaussian distributed value corresponding to u.
process_name opflash particleana ie ie ie z
Data_t mean() const
Returns the mean of the target Gaussian distribution.
unsigned int const fN
The number of points the interval is split into.
Data_t const fOffset
Offset of the values (half step).
T Data_t
Type of data required.
static std::array< Data_t, N > const fSamples
Sampled points of inverse Gaussian.
std::size_t indexOf(Data_t u) const
Returns the index of the precomputed table serving the value u.
double Data_t
Type of data to deal with.
Data_t operator()(Data_t const z) const
Translates a number u uniformly distributed between 0 and 1 into a Gaussian distributed one z...
unsigned int fNext
The next point to be delivered.
Data_t transform(Data_t const z) const
Transforms normal value z into the target distribution.
Data_t operator()(Data_t const u) const
constexpr bool isPowerOfTwo(T value)
Returns whether the integral type value is a power of 2.
static constexpr std::size_t NPoints
Number of sampled points.
unsigned int next()
Returns the current point and prepares for the next one.
static std::array< Data_t, NPoints > makeSamples()
Fills the pre-sampling table.
Transforms a standard normal number into one on a different normal distribution.
Data_t extract()
Returns the next number in the sequence.
Data_t stdDev() const
Returns the standard deviation of the target Gaussian distribution.
UniformSequence(unsigned int N)
Initializes a sequence of N steps.
GaussianTransformer(T mean, T stddev)
Constructor: selects the mean and standard deviation.
process_name largeant stream1 can override from command line with o or output physics producers generator N
void reset()
Restarts the sequence.
temporary value
Data_t const fStep
Size of each step.
Samples the interval [ 0, 1 ] in sequence, cyclically.