Random.cpp
Go to the documentation of this file.
1 // (C) Copyright Renaud Detry 2007-2015.
2 // Distributed under the GNU General Public License and under the
3 // BSD 3-Clause License (See accompanying file LICENSE.txt).
4 
5 /** @file */
6 
7 #include <iostream>
8 #include <gsl/gsl_rng.h>
9 #include <gsl/gsl_randist.h>
10 
11 #include <boost/thread/mutex.hpp>
12 #include <boost/thread/thread.hpp>
13 
14 #include <nuklei/Random.h>
15 #include <nuklei/Common.h>
16 #include <nuklei/Log.h>
17 
18 #include <boost/random.hpp>
19 
20 namespace nuklei {
21 
22 // Either use GSL random gen, or Boost random gen
23 //#define NUKLEI_USE_BOOST_RANDOM_GEN
24 
25 // Sync before accessing random gen?
26 // -> NUKLEI_RANDOM_SYNC_OMP: use OMP exclusive access
27 // This one is obsolete, because when OMP is enabled, omp_get_thread_num
28 // allows Random methods to access thread-specific random gens.
29 // -> NUKLEI_RANDOM_SYNC_MUTEX: use posix mutex
30 // This makes Nuklei much slower
31 // -> NUKLEI_RANDOM_SYNC_NONE: default, use this on single-thread, or with OMP.
32 
33 #define NUKLEI_RANDOM_SYNC_NONE
34 
35 #ifdef _OPENMP
36 #include <omp.h>
37  static inline int nuklei_thread_num()
38  {
39  return omp_get_thread_num();
40  }
41  static inline int nuklei_max_threads()
42  {
43  // I had issues with omp_get_max_threads(), returning 1 while spawning
44  // more than one thread.
45  // I had this with static executables.
46  // I guess it could be a race condition.
47  // Let's just return a large number instead (who would have more than 1000
48  // procs anyway?)
49  //return omp_get_max_threads();
50  return 1024;
51  }
52 #else
53  static inline int nuklei_thread_num()
54  {
55  return 0;
56  }
57  static inline int nuklei_max_threads()
58  {
59  return 1;
60  }
61 #endif
62 
63 
64  // bRandGens must be a pointer. If not, its construtor may be called after
65  // init() is called, which will destroy the bRandGens setup in init().
66  static std::vector<boost::mt19937>* bRandGens;
67  // Same holds for these:
68  static std::vector<gsl_rng*>* gRandGens;
69  static std::vector<boost::shared_ptr<boost::mutex> >* mutexes;
70  // Keep in mind that it's unsafe to store pointers in std::vectors because
71  // they could be discarted without being deallocated. Here it's fine as we
72  // won't be resizing or deleting the vector before the end if the program.
73 
74  bool Random::initialized_ = Random::init();
75 
76  bool Random::init()
77  {
78  unsigned seed = 0;
79 
80  const char * envValPara = getenv("NUKLEI_PARALLELIZATION");
81  if (envValPara != NULL)
82  {
83  std::string para(envValPara);
84  if (para == "single" || para == "openmp")
85  {
86  // all ok
87  }
88  else if (para == "pthread")
89  {
90 #if defined(NUKLEI_RANDOM_SYNC_OMP) || defined(NUKLEI_RANDOM_SYNC_NONE) || !defined(NUKLEI_RANDOM_SYNC_MUTEX)
91  std::cout << "NUKLEI_PARALLELIZATION is set to pthread. "
92  "You should manually undefine all NUKLEI_RANDOM_SYNC_* in Random.cpp, "
93  "and define NUKLEI_RANDOM_SYNC_MUTEX instead. Note that multithreading "
94  "will be slower than with OpenMP. "
95  "See http://renaud-detry.net/nuklei/group__faq.html" << std::endl;
96  std::terminate();
97 #endif
98  }
99  else
100  {
101  std::cout << "Unknown value '" << para << "' for NUKLEI_PARALLELIZATION"
102  " env variable." << std::endl;
103  std::terminate();
104  }
105  }
106 
107  const char * envVal = getenv("NUKLEI_RANDOM_SEED");
108  if (envVal != NULL)
109  {
110  const char * log = getenv("NUKLEI_LOG_LEVEL");
111  if (log != NULL && numify<unsigned>(log) >= Log::INFO)
112  std::cout << "export " << "NUKLEI_RANDOM_SEED" << "="
113  << numify<int>(envVal)
114  << "\n";
115  double seed_d = numify<double>(envVal);
116  if (seed_d >= 0)
117  seed = numify<unsigned>(envVal);
118  else
119  seed = time(NULL)*getpid(); // Unsigned don't overflow, they wrap around
120  }
121 
122  bRandGens = new std::vector<boost::mt19937>(nuklei_max_threads());
123  gRandGens = new std::vector<gsl_rng*>(nuklei_max_threads(), NULL);
124  for (int i = 0; i < nuklei_max_threads(); i++)
125  gRandGens->at(i) = gsl_rng_alloc(gsl_rng_mt19937);
126 
127  mutexes = new std::vector<boost::shared_ptr<boost::mutex> >();
128  for (int i = 0; i < nuklei_max_threads(); i++)
129  mutexes->push_back(boost::shared_ptr<boost::mutex>(new boost::mutex()));
130 
132  return true;
133  }
134 
135  void Random::seed(unsigned s)
136  {
137  if (getenv("NUKLEI_RANDOM_SEED") != NULL)
138  {
139  // Libraries Nuklei depends on may make use of random numbers.
140  // Let's make sure we seed those randomly as well.
141  ::srandom(s);
142  //BSD implementation of rand differs from random.
143  ::srand(s);
144  }
145 
146  for (int i = 0; i < bRandGens->size(); ++i)
147  {
148  bRandGens->at(i).seed(s+i);
149  }
150  for (int i = 0; i < gRandGens->size(); ++i)
151  {
152  gsl_rng_set(gRandGens->at(i), s+i+1); // +1 because GSL complains when seed == 0
153  }
154  }
155 
156  //This function returns a double precision floating point number
157  //uniformly distributed in the range [0,1). The range includes 0.0 but
158  //excludes 1.0.
160  {
161  double r;
162 #if defined(NUKLEI_RANDOM_SYNC_OMP)
163 # pragma omp critical(nuklei_randomRng)
164 #elif defined(NUKLEI_RANDOM_SYNC_MUTEX)
165  boost::unique_lock<boost::mutex> lock(*mutexes->at(nuklei_thread_num()));
166 #elif defined(NUKLEI_RANDOM_SYNC_NONE)
167 #else
168 # error Undefined random sync method
169 #endif
170 #ifdef NUKLEI_USE_BOOST_RANDOM_GEN
171  {
172  boost::uniform_01<> dist;
173  boost::variate_generator<boost::mt19937&, boost::uniform_01<> >
174  die(bRandGens->at(nuklei_thread_num()), dist);
175  r = die();
176  }
177 #else
178  r = gsl_rng_uniform(gRandGens->at(nuklei_thread_num()));
179 #endif
180  return r;
181  }
182 
183  //This function returns a double precision floating point number
184  //uniformly distributed in the range [a,b). The range includes a but
185  //excludes b.
186  double Random::uniform(double a, double b)
187  {
188  NUKLEI_FAST_ASSERT(a < b);
189  return a + uniform()*(b-a);
190  }
191 
192  //This function returns a random integer from 0 to n-1 inclusive by
193  //scaling down and/or discarding samples from the generator r. All
194  //integers in the range [0,n-1] are produced with equal probability.
195  unsigned long int Random::uniformInt(unsigned long int n)
196  {
197  unsigned long int r;
198  // GSL has trouble with concurrent random number generation:
199  // - if a single generator is used, it must be mutexed.
200  // - using one random generator per thread is somehow very slow
201  // Random::uniformInt is used *a lot*, everytime a KernelCollection is
202  // iterated in random order. Using a mutex here entirely breaks
203  // multithreading (n threads on n cpus takes as much time as the same work
204  // on a single cpu). Multithreading will only be fast if done through
205  // OpenMP. This is because it is hard to map pthreads to a
206  // number. boost::thread::id could be used to implement nuklei_thread_num()
207  // (todo?), but random generators could not be cleaned when a thread exits.
208 #if defined(NUKLEI_RANDOM_SYNC_OMP)
209 # pragma omp critical(nuklei_randomRng)
210 #elif defined(NUKLEI_RANDOM_SYNC_MUTEX)
211  boost::unique_lock<boost::mutex> lock(*mutexes->at(nuklei_thread_num()));
212 #elif defined(NUKLEI_RANDOM_SYNC_NONE)
213 #else
214 # error Undefined random sync method
215 #endif
216 #ifdef NUKLEI_USE_BOOST_RANDOM_GEN
217  {
218  boost::uniform_int<unsigned long> dist(0, n-1);
219  boost::variate_generator<boost::mt19937&, boost::uniform_int<unsigned long> >
220  die(bRandGens->at(nuklei_thread_num()), dist);
221  r = die();
222  }
223 #else
224  r = gsl_rng_uniform_int(gRandGens->at(nuklei_thread_num()), n);
225 #endif
226  return r;
227  }
228 
229  double Random::triangle(double b)
230  {
231  double r;
232 #if defined(NUKLEI_RANDOM_SYNC_OMP)
233 # pragma omp critical(nuklei_randomRng)
234 #elif defined(NUKLEI_RANDOM_SYNC_MUTEX)
235  boost::unique_lock<boost::mutex> lock(*mutexes->at(nuklei_thread_num()));
236 #elif defined(NUKLEI_RANDOM_SYNC_NONE)
237 #else
238 # error Undefined random sync method
239 #endif
240  {
241  boost::triangle_distribution<> dist(-b/2, 0, b/2);
242  boost::variate_generator<boost::mt19937&, boost::triangle_distribution<> >
243  die(bRandGens->at(nuklei_thread_num()), dist);
244  r = die();
245  }
246  return r;
247  }
248 
249  //This function returns a Gaussian random variate, with mean zero and
250  //standard deviation sigma. Use the transformation z = \mu + x on the
251  //numbers returned by gsl_ran_gaussian to obtain a Gaussian distribution
252  //with mean \mu.
253  double Random::gaussian(double sigma)
254  {
255  double r;
256 #if defined(NUKLEI_RANDOM_SYNC_OMP)
257 # pragma omp critical(nuklei_randomRng)
258 #elif defined(NUKLEI_RANDOM_SYNC_MUTEX)
259  boost::unique_lock<boost::mutex> lock(*mutexes->at(nuklei_thread_num()));
260 #elif defined(NUKLEI_RANDOM_SYNC_NONE)
261 #else
262 # error Undefined random sync method
263 #endif
264 #ifdef NUKLEI_USE_BOOST_RANDOM_GEN
265  {
266  boost::normal_distribution<> dist(0, sigma);
267  boost::variate_generator<boost::mt19937&, boost::normal_distribution<> >
268  die(bRandGens->at(nuklei_thread_num()), dist);
269  r = die();
270  }
271 #else
272  r = gsl_ran_gaussian(gRandGens->at(nuklei_thread_num()), sigma);
273 #endif
274  return r;
275  }
276 
277  double Random::beta(double a, double b)
278  {
279  double r;
280 #if defined(NUKLEI_RANDOM_SYNC_OMP)
281 # pragma omp critical(nuklei_randomRng)
282 #elif defined(NUKLEI_RANDOM_SYNC_MUTEX)
283  boost::unique_lock<boost::mutex> lock(*mutexes->at(nuklei_thread_num()));
284 #elif defined(NUKLEI_RANDOM_SYNC_NONE)
285 #else
286 # error Undefined random sync method
287 #endif
288  r = gsl_ran_beta(gRandGens->at(nuklei_thread_num()), a, b);
289  return r;
290  }
291 
293  {
294  Vector2 dir;
295 #if defined(NUKLEI_RANDOM_SYNC_OMP)
296 # pragma omp critical(nuklei_randomRng)
297 #elif defined(NUKLEI_RANDOM_SYNC_MUTEX)
298  boost::unique_lock<boost::mutex> lock(*mutexes->at(nuklei_thread_num()));
299 #elif defined(NUKLEI_RANDOM_SYNC_NONE)
300 #else
301 # error Undefined random sync method
302 #endif
303 #ifdef NUKLEI_USE_BOOST_RANDOM_GEN
304  {
305  const int dim = 2;
306  typedef boost::uniform_on_sphere<double, std::vector<double> > dist_t;
307  dist_t dist(dim);
308  boost::variate_generator<boost::mt19937&, dist_t >
309  die(bRandGens->at(nuklei_thread_num()), dist);
310  std::vector<double> r = die();
311  dir.X() = r.at(0);
312  dir.Y() = r.at(1);
313  }
314 #else
315  gsl_ran_dir_2d(gRandGens->at(nuklei_thread_num()), &dir.X(), &dir.Y());
316 #endif
317  return dir;
318  }
319 
321  {
322  Vector3 dir;
323 #if defined(NUKLEI_RANDOM_SYNC_OMP)
324 # pragma omp critical(nuklei_randomRng)
325 #elif defined(NUKLEI_RANDOM_SYNC_MUTEX)
326  boost::unique_lock<boost::mutex> lock(*mutexes->at(nuklei_thread_num()));
327 #elif defined(NUKLEI_RANDOM_SYNC_NONE)
328 #else
329 # error Undefined random sync method
330 #endif
331 #ifdef NUKLEI_USE_BOOST_RANDOM_GEN
332  const int dim = 3;
333  typedef boost::uniform_on_sphere<double, std::vector<double> > dist_t;
334  dist_t dist(dim);
335  boost::variate_generator<boost::mt19937&, dist_t >
336  die(bRandGens->at(nuklei_thread_num()), dist);
337  std::vector<double> r = die();
338  dir.X() = r.at(0);
339  dir.Y() = r.at(1);
340  dir.Z() = r.at(2);
341 #else
342  gsl_ran_dir_3d(gRandGens->at(nuklei_thread_num()), &dir.X(), &dir.Y(), &dir.Z());
343 #endif
344  return dir;
345  }
346 
348  {
349  // See Kuffner 2004 and Shoemake 1992.
350  // A supposably "slightly faster" way could be read from
351  // http://planning.cs.uiuc.edu/node198.html and Arvo 1992, but
352  // would need to be tested.
353  // Also, how would gsl_ran_dir_nd perform?
354 
355 #if defined(NUKLEI_RANDOM_QUATERNION_MARSAGLIA_1972)
356  coord_t x1, y1, s1, x2, y2, s2;
357  for (;;)
358  {
359  x1 = uniform(-1, 1);
360  y1 = uniform(-1, 1);
361  s1 = x1*x1 + y1*y1;
362  if (s1 < 1) break;
363  }
364  for (;;)
365  {
366  x2 = uniform(-1, 1);
367  y2 = uniform(-1, 1);
368  s2 = x2*x2 + y2*y2;
369  if (s2 < 1) break;
370  }
371  coord_t root = std::sqrt( (1-s1)/s2 );
372  Quaternion u(x1,
373  y1,
374  x2 * root,
375  y2 * root);
376  return q;
377 #elif defined(NUKLEI_RANDOM_QUATERNION_GAUSSIAN_PROJ)
378  // comparable to gsl_ran_dir_nd
379  Quaternion u(gaussian(1),
380  gaussian(1),
381  gaussian(1),
382  gaussian(1));
383  u.Normalize();
384  return u;
385 #else // Fastest method (although MARSAGLIA is comparable)
386  coord_t s = static_cast<coord_t>(uniform());
387  //assert(s <= 1 && s >= 0);
388  coord_t s1 = std::sqrt(1-s);
389  coord_t s2 = std::sqrt(s);
390  coord_t t1 = 2 * M_PI * static_cast<coord_t>(uniform());
391  coord_t t2 = 2 * M_PI * static_cast<coord_t>(uniform());
392  Quaternion u(std::cos(t2) * s2,
393  std::sin(t1) * s1,
394  std::cos(t1) * s1,
395  std::sin(t2) * s2);
396  //assert(nuklei_wmf::Math<coord_t>::FAbs(u.Length()-1) < 1e-6);
397  return u;
398 #endif
399  }
400 
401  void Random::printRandomState()
402  {
403  NUKLEI_INFO("Random state: " <<
404  NUKLEI_NVP(random()) <<
405  "\n " << NUKLEI_NVP(rand()) <<
406  "\n " << NUKLEI_NVP(Random::uniformInt(1000000)) <<
407  "\n " << NUKLEI_NVP(Random::uniform()));
408  }
409 
410 }
static double beta(double a, double b)
This function returns a random variate from the beta distribution.
Definition: Random.cpp:277
Public namespace.
Definition: Color.cpp:9
static double triangle(double b)
This function returns a random variate from the triangle distribution of zero mean and base b.
Definition: Random.cpp:229
static Vector2 uniformDirection2d()
This function returns a random direction vector in two dimensions.
Definition: Random.cpp:292
static unsigned long int uniformInt(unsigned long int n)
This function returns a random integer from 0 to n-1 inclusive.
Definition: Random.cpp:195
static void seed(unsigned s)
Seeds the random generators with s.
Definition: Random.cpp:135
static Quaternion uniformQuaternion()
This function returns rotation uniformly distributed on .
Definition: Random.cpp:347
double coord_t
Type for point coordinates.
Definition: Definitions.h:25
#define NUKLEI_NVP(x)
Name-value pair.
Definition: Common.h:76
Definition: GenericKernel.h:73
static bool init()
Used internally.
Definition: Random.cpp:76
static double uniform()
This function returns a double precision floating point number uniformly distributed in the range .
Definition: Random.cpp:159
static Vector3 uniformDirection3d()
This function returns a random direction vector in three dimensions.
Definition: Random.cpp:320
static double gaussian(double sigma)
This function returns a Gaussian random variate, with mean zero and standard deviation sigma.
Definition: Random.cpp:253
© Copyright 2007-2013 Renaud Detry.
Distributed under the terms of the GNU General Public License (GPL).
(See accompanying file LICENSE.txt or copy at http://www.gnu.org/copyleft/gpl.html.)
Revised Sun Sep 13 2020 19:10:06.