8

I am given to believe that random number generators (RNGs) should only be seeded once to ensure that the distribution of results is as intended.

I am writing a Monte Carlo simulation in C++ which consists of a main function ("A") calling another function ("B") several times, where a large quantity of random numbers is generated in B.

Currently, I am doing the following in B:

void B(){
    std::array<int, std::mt19937::state_size> seed_data;
    std::random_device r;

    std::generate(seed_data.begin(), seed_data.end(), std::ref(r));
    std::seed_seq seq(std::begin(seed_data), std::end(seed_data)); //perform warmup
    std::mt19937 eng(seq);

    std::uniform_real_distribution<> randU(0,1);

    double myRandNum = randU(eng);

    //do stuff with my random number
}

As you can see, I am creating a new random number generator each time I call the function B. This, as far as I can see, is a waste of time - the RNG can still generate a lot more random numbers!

I have experimented with making "eng" extern but this generates an error using g++:

error: ‘eng’ has both ‘extern’ and initializer extern std::mt19937 eng(seq);

How can I make the random number generator "global" so that I can use it many times?

GnomeSort
  • 275
  • 3
  • 12

2 Answers2

11

Be careful with one-size-fits-all rules. 'Globals are evil' is one of them. A RNG should be a global object. (Caveat: each thread should get its own RNG!) I tend to wrap mine in a singleton map, but simply seeding and warming one up at the beginning of main() suffices:

std::mt19937 rng;

int main()
{
  // (seed global object 'rng' here)
  rng.dispose(10000); // warm it up

For your usage scenario (generating multiple RNs per call), you shouldn't have any problem creating a local distribution for each function call.

One other thing: std::random_device is not your friend -- it can throw at any time for all kinds of stupid reasons. Make sure to wrap it up in a try..catch block. Or, and I recommend this, use a platform specific way to get a true random number. (On Windows, use the Crypto API. On everything else, use /dev/urandom/.)

Hope this helps.

Dúthomhas
  • 8,200
  • 2
  • 17
  • 39
  • Very helpful, thank you. But how do I seed `rng` now? I tried (as an example) `eng(r);` where `r` is an `std::random_device` but I get the error "error: no match for call to ‘(std::mt19937 {aka std::mersenne_twister_engine [...cut out because extremely long...] /usr/include/c++/4.9/bits/random.h:546:7: note: candidate expects 0 arguments, 1 provided". How do you seed it? – GnomeSort Nov 26 '15 at 05:28
  • 2
    Use the `seed()` member function: `rng.seed(your_seed_sequence)`. – Dúthomhas Nov 26 '15 at 05:32
  • 2
    why would you need to warm up a random number generator? I have never heard about warming up generators. – Marine Galantin Feb 12 '22 at 15:05
  • 1
    @MarineGalantin “Warming up” gets rid of potential undesirable (i.e. predictable) initial state. [Here’s more about using MT specifically](https://stackoverflow.com/a/15509942/2706707). – Dúthomhas Aug 10 '22 at 19:25
1

You shouldn't need to pass anything or declare anything, as the interaction between mt19937 and uniform_real_distribution is through globals.

std::array<int, std::mt19937::state_size> seed_data;
std::random_device r;

std::generate(seed_data.begin(), seed_data.end(), std::ref(r));
std::seed_seq seq(std::begin(seed_data), std::end(seed_data)); //perform warmup
std::mt19937 eng(seq);


B()

...

void B() 
{

    std::uniform_real_distribution<> randU(0,1);
...
Dominique McDonnell
  • 2,510
  • 16
  • 25
  • 1
    Thanks for your answer. But I want the std::mt19937 to be the object that I pass by reference, and I haven't been able to declare it outside function B because it seems I cannot initialize outside function B ( std::mt1997 eng; ) and then later say inside the function, ( eng(seq); ). How do I get around this? – GnomeSort Nov 26 '15 at 03:50
  • `eng(seq)` is calling the constructor. Do you want to reseed or apply a new seed? Then you need to do `eng.seed(seq);` – Dominique McDonnell Nov 26 '15 at 03:57
  • 1
    I only want to seed once. Then call `randU(eng)` each time thereafter to get a random number. – GnomeSort Nov 26 '15 at 03:59
  • So have you tried doing the initialisation in A() and then just using `std::uniform_real_distribution<> randU(0,1);` in B()? – Dominique McDonnell Nov 26 '15 at 04:03
  • Seems to me to already be using global objects and you don't actually need to pass anything. – Dominique McDonnell Nov 26 '15 at 04:05