31

I'm working on a program that runs Monte Carlo simulation; specifically, I'm using a Metropolis algorithm. The program needs to generate possibly billions of "random" numbers. I know that the Mersenne twister is very popular for Monte Carlo simulation, but I would like to make sure that I am seeding the generator in the best way possible.

Currently I'm computing a 32-bit seed using the following method:

mt19937_64 prng; //pseudo random number generator
unsigned long seed; //store seed so that every run can follow the same sequence
unsigned char seed_count; //to help keep seeds from repeating because of temporal proximity

unsigned long genSeed() {
    return (  static_cast<unsigned long>(time(NULL))      << 16 )
         | ( (static_cast<unsigned long>(clock()) & 0xFF) << 8  )
         | ( (static_cast<unsigned long>(seed_count++) & 0xFF) );
}

//...

seed = genSeed();
prng.seed(seed);

I have a feeling there are much better ways to assure non-repeating new seeds, and I'm quite sure mt19937_64 can be seeded with more then 32-bits. Does anyone have any suggestions?

Praetorian
  • 100,267
  • 15
  • 224
  • 307
Mathhead200
  • 331
  • 3
  • 6
  • 3
    Why does it matter? Why do you need to ensure that different runs of your simulation get different seeds? Why do you need to go out of your way to do this? It won't give you "better" random numbers. – jalf Jul 04 '14 at 18:05
  • Because we may run the simulation with the same set of parameters, in which case we don't necessarily expect the exact same results (which is what would happen if we used the same seed.) – Mathhead200 Jul 06 '14 at 21:53
  • 3
    Sure, but seeding with something as simple as a timestamp would ensure that. Why do you need the NASA-levels of complexity to absolutely *guarantee* that... I don't even know what it is you are trying to guarantee. It sounds absurdly overengineered. – jalf Jul 08 '14 at 08:03
  • @jalf The timestamp from time() in only has one second precision. But even if I was using millisecond precision (or whatever) it's likely that many of the simulations would start with the same seed. I'm running several of these simulations concurrently, (usually started programically in separate threads.) – Mathhead200 Jul 08 '14 at 10:05
  • 1
    And yet you say nothing about this in your question. You ask for "the best seed", which is a nonsensical question to ask. What you apparently *wanted* answered is "how do I select seeds so that different threads (or processes?), even if they are started simultaneously, have a minimal chance of choosing the same seeds". That is a reasonable question. But it has nothing to do with choosing "the best seed". You should update your question to ask the actual question you want answered. – jalf Jul 08 '14 at 14:26
  • @jalf Sorry. I thought I explained what I meant when I said, "I have a feeling there are much better ways to assure non-repeating new seeds, and I'm quite sure mt19937_64 can be seeded with more then 32-bits. Does anyone have any suggestions?" I suppose including that many copies of this code could run very close together in time would have helped, but I did put a comment in my code: "//to help keep seeds from repeating because of temporal proximity" – Mathhead200 Jul 09 '14 at 04:22
  • Well I did not get the answer I hoped for, but hopefully you got some answers that helped you @Mathhead200. If you have any preferences who should earn the bounty, just leave a comment. – dyp Jul 11 '14 at 12:47
  • This comment is to hopefully clear up a few things about my initial post. I purposely left out information like multithreading, and distributed systems (which is how we are running it now), because I don't know how this code will be used in the future, and on what machines. (e.g. We were looking at moving the program to a cloud or grid, or even a super computer.) I want to make sure I'm seeding mt19937_64 in the best way possible (ideally different seeds each time), in context to the algorithm it self, and without making too many assumptions about how this program will be run. – Mathhead200 Jul 11 '14 at 20:06
  • @dyp That's okay. I did get some very useful information and insight into the issue at hand. Also, people are still posting and I'm hopeful that someone who know much more then me will come along with a nice solution that has evaded me thus far. (So far yours is still the most elegant and seemingly platform independent solution.) – Mathhead200 Jul 11 '14 at 20:14

6 Answers6

26

Use std::random_device to generate the seed. It'll provide non-deterministic random numbers, provided your implementation supports it. Otherwise it's allowed to use some other random number engine.

std::mt19937_64 prng;
seed = std::random_device{}();
prng.seed(seed);

operator() of std::random_device returns an unsigned int, so if your platform has 32-bit ints, and you want a 64-bit seed, you'll need to call it twice.

std::mt19937_64 prng;
std::random_device device;
seed = (static_cast<uint64_t>(device()) << 32) | device();
prng.seed(seed);

Another available option is using std::seed_seq to seed the PRNG. This allows the PRNG to call seed_seq::generate, which produces a non-biased sequence over the range [0 ≤ i < 232), with an output range large enough to fill its entire state.

std::mt19937_64 prng;
std::random_device device;
std::seed_seq seq{device(), device(), device(), device()};
prng.seed(seq);

I'm calling the random_device 4 times to create a 4 element initial sequence for seed_seq. However, I'm not sure what the best practice for this is, as far as length or source of elements in the initial sequence is concerned.

Praetorian
  • 100,267
  • 15
  • 224
  • 307
  • AFAIK, this uses "only" a 64 bit seed. I'm not sure this is enough for every purpose. I still wonder how to elegantly use `random_device` to provide as much seed as the PRNG can make use of. – dyp Jun 20 '14 at 19:38
  • @dyp There's `std::seed_seq` that you could feed with multiple calls to `random_device`, and then pass that to `mt19937_64::seed`. According to [cppreference](http://en.cppreference.com/w/cpp/numeric/random/seed_seq) `seed_seq` will generate results that are distributed over [0 ≤ i < 2^32), but I have no idea whether doing that is better than bit shifting, or how many times you'd need to call `random_device` for constructing the input range to `seed_seq` (meaning how many elements should the input have for it to be considered *good*). – Praetorian Jun 20 '14 at 19:47
  • The interesting part about seed sequences is that the MT can request as much seed as it wants to, that can be more than just 64 bit. I'm not sure if using `seed_seq` in combination with `random_device` is useful, that depends on the behaviour/requirements which MT has (`seed_seq::generate` eliminates bias). – dyp Jun 20 '14 at 19:50
  • [Something like this](http://coliru.stacked-crooked.com/a/bca8c7d2e90d249e) (maybe with more member functions implemented); but as I said, I don't know if the output of `uniform_int_distribution` + `random_device` is good for MT. – dyp Jun 20 '14 at 20:01
  • @dyp Found something interesting ... according to Table 117, seeding using a seed sequence *Creates an engine with an initial state that depends on a sequence produced by one call to q.generate*. So in this case using that option will result in the `mt19937_64` being seeded by a 32-bit seed. I'm not saying that that is a bad thing, don't know enough about this stuff to make such claims. – Praetorian Jun 20 '14 at 20:07
  • 1
    Huh? I don't quite understand what you mean. The example code I posted seeds with 624 32-bit quantities (see http://coliru.stacked-crooked.com/a/5e754fc72c89ed59 ), which is 19968 bit and therefore probably fills the entire state of 19937 bit. – dyp Jun 20 '14 at 20:18
  • @dyp Never mind, I didn't notice that `generate` takes a pair of iterators and fills the range. I assumed it returns a 32-bit value. – Praetorian Jun 20 '14 at 20:47
  • So your saying I could do something like `seed_seq({random_device(), time(NULL), clock(), seed_count++, ...})`, or would just using one output of random_device be better? Also, can I still used seed_seq if, in the future, I want to be able to generate the same sequence of "random" numbers? (In other words, if I store the initializer list of seed_seq and reconstruct a new object using the same list, will it produce the same seed sequence and seed mt19937_64 exactly the same way?) – Mathhead200 Jun 22 '14 at 01:06
  • @Mathhead200 The type of each of those things you've listed within the braced-init-list need not be the same, so deduction of the `initializer_list` could fail. You could do that if you specified the type `seed_seq(std::initializer_list{random_device(), time(NULL), clock(), seed_count++, ...})`, but even may not work because one of the types involved could be `unsigned long long`. If you want to do that, then cast each of them to the same type. – Praetorian Jun 22 '14 at 01:26
  • @Mathhead200 Like I said at the end, I don't know what the *best* way is, I'd think using `seed_seq` is a *good* way, but again, I have no idea how many initializers to use, or whether you should use `random_device` or not. If you save the initializers to `seed_seq` it will produce the same output each time when initialized with that sequence, since it is deterministic. I've only used `std::mt19937` once, and that time I seeded it with a single call to `random_device`. – Praetorian Jun 22 '14 at 01:27
  • @Praetorian Okay, I wasn't being literal with the code though; I'm sure I would have caught the type irregularities. Thanks for the input. (Too bad no one else has responded yet.) Do you think a single call to random_device would be a better way to go then using `time() + clock() + ...` (like I am above)? How good is random_device on a standard Windows 7 professional setup? What about if we ever change platforms? Do any super computing or grid/cloud computing platforms not support random_device well? – Mathhead200 Jun 23 '14 at 04:44
  • @Mathhead200 You're refusing to listen to me :) I don't know enough about this stuff to recommend seeding using a single call to `random_device` vs `seed_seq`. If the quality of random numbers generated is critical to your application, you should contact an expert on the matter. You should be able to look through the MSVC implementation of `random_device`. IIRC, it ignores the optional argument and calls some Windows CryptoAPI function. And I have no idea about its availability on cloud computing platforms. I'd imagine if these platforms have a C++ stdlib implementation, it'll be available – Praetorian Jun 24 '14 at 00:05
6

Let's recap (comments too), we want to generate different seeds to get independent sequences of random numbers in each of the following occurrences:

  1. The program is relaunched on the same machine later,
  2. Two threads are launched on the same machine at the same time,
  3. The program is launched on two different machines at the same time.

1 is solved using time since epoch, 2 is solved with a global atomic counter, 3 is solved with a platform dependent id (see How to obtain (almost) unique system identifier in a cross platform way?)

Now the point is what is the best way to combine them to get a uint_fast64_t (the seed type of std::mt19937_64)? I assume here that we do not know a priori the range of each parameter or that they are too big, so that we cannot just play with bit shifts getting a unique seed in a trivial way.

A std::seed_seq would be the easy way to go, however its return type uint_least32_t is not our best choice.

A good 64 bits hasher is a much better choice. The STL offers std::hash under the functional header, a possibility is to concatenate the three numbers above into a string and then passing it to the hasher. The return type is a size_t which on 64 machines is very likely to match our requirements.

Collisions are unlikely but of course possible, if you want to be sure to not build up statistics that include a sequence more than once, you can only store the seeds and discard the duplicated runs.

A std::random_device could also be used to generate the seeds (collisions may still happen, hard to say if more or less often), however since the implementation is library dependent and may go down to a pseudo random generator, it is mandatory to check the entropy of the device and avoid to a use zero-entropy device for this purpose as you will probably break the points above (especially point 3). Unfortunately you can discover the entropy only when you take the program to the specific machine and test with the installed library.

Community
  • 1
  • 1
DarioP
  • 5,010
  • 1
  • 28
  • 48
  • Thanks, perfect summarization. I'm going to look at that SO link on obtaining unique system identifiers. MY main consern being I don't know what machines this program will be run on in the future. (Same issue with `random_device`.) -- I thought, in general, Mersenne Twister was seeded with more then 64 bits...? – Mathhead200 Jul 09 '14 at 19:57
  • @Mathhead200 The STL implementation takes a 64 bits seed, however the original C implementation should take arbitrary long seeds, see http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/efaq.html (look for `init_by_array`) This would save you the usage of the hash function and maybe lower the probability of a collision. However having an already cooked STL implementation I'm not sure it is worth it. – DarioP Jul 10 '14 at 06:49
  • 1
    I think `seed_seq` has been made for supplying arbitrary long seeds. Take a look at [`seed_seq::generate`](http://en.cppreference.com/w/cpp/numeric/random/seed_seq/generate). `generate` generates multiple 32-bit values, but `mt19937_64` does not need to use a state based on 64-bit data types; even if, you can still use 32-bit values to fill it (via distributions / adapters). – dyp Jul 12 '14 at 11:58
  • @dyp I do not know what percentage of the sequences of parameters that generate a collision at the first call of `seed_seq::generate` a will still collide also at the second call. Not being able to exclude that it's 100%, I prefer to go for a real 64 bits hasher. `mt19937_64` accepts a `uint_fast64_t` seed, you can pass a 32 bits value, but if you have a real 64 bits number, in principle you can get a much greater number of different sequences. – DarioP Jul 12 '14 at 19:47
  • I don't quite understand why you want to call `generate` multiple times. – dyp Jul 12 '14 at 21:38
  • @dyp I meant multiple calls to the internal algorithm of the seed_seq through a call to generate on an array of 32 bits numbers. – DarioP Jul 13 '14 at 09:01
  • `seed_seq` has been made to produce unbiased, uniformly distributed 32-bit values from *biased* (and very similar) inputs. – dyp Jul 13 '14 at 10:16
  • @dyp ...and the seed of the MT64 generator is 64 bits. Please, re-read the comments above once again. – DarioP Jul 13 '14 at 11:25
  • 1
    The *state* of a MT19937 has 19937 bits. One constructor of a `mt19937_64` takes a single 64-bit seed and computes the initial state from this seed. Another constructor takes a SeedSequence and its `generate` method to fill its state. [Some implementations](http://coliru.stacked-crooked.com/a/5e754fc72c89ed59) extract 19968 bits from the seed sequence; enough to fill the entire state. – dyp Jul 13 '14 at 11:43
  • @dyp That's clear, my concerns are just about the fact that seed_seq goes through 32 bits numbers. A bad implementation may have a high collision rate (see above). – DarioP Jul 13 '14 at 12:59
  • 1
    The algorithm `seed_seq::generate` uses is fully specified in the Standard. I do not know the exact mathematical properties of that algorithm. It has been invented specifically to seed an MT from a (biased) array of arbitrary length. The algorithm that the `std::mersenne_twister_engine` uses to initialize its state from the generated values is also fully specified (something I just learned). So as far as I can tell, there will be no high collision rate: Those tools are specifically designed for exactly this purpose. – dyp Jul 13 '14 at 14:49
4

As far as I can tell from your comments, it seems that what you are interested in is ensuring that if a process starts several of your simulations at exactly the same time, they will get different seeds.

The only significant problem I can see with your current approach is a race condition: if you are going to start multiple simulations simultaneously, it must be done from separate threads. If it is done from separate threads, you need to update seed_count in a thread-safe manner, or multiple simulations could end up with the same seed_count. You could simply make it an std::atomic<int> to solve that.

Beyond that, it just seems more complicated than it has to be. What do you gain by using two separate timers? You could do something as simple as this:

  1. at program startup, grab the current system time (using a high resolution timer) once, and store that.
  2. assign each simulation a unique ID (this could just be an integer initialized to 0, (which should be generated without any race conditions, as mentioned above) which is incremented each time a simulation starts, effectively like your seed_count.
  3. when seeding a simulation, just use the initially generated timestamp + the unique ID. If you do this, every simulation in the process is assured a unique seed.
jalf
  • 229,000
  • 47
  • 328
  • 537
  • That is what I was trying to do with the above code. However, the code is a snip-it from a class, `MSD`, and these are actually member variables. (Perhaps making `seed_count` static is warranted.) In the multi-threaded case, each thread has its own instance of `MSD` and therefor, for that case, seed_count isn't helping. (Again making `seed_count` static should fix this.) Another problem is these tasks could be split between multiple computers, in which case the about talked about solutions don't apply. – Mathhead200 Jul 09 '14 at 04:32
1

How about...

There is some main code that starts the threads and there are copies of a function run in those threads, each copy with it's own Marsenne Twister. Am I correct? If it is so, why not use another random generator in the main code? It would be seeded with time stamp, and send it's consecutive pseudorandom numbers to function instances as their seeds.

LordViaderko
  • 121
  • 3
  • I could do this, (although I'd rather the sequences just be independent); however, this doesn't address the problem of running the simulations on multiple machines. Also, it seems like a lot more work then just messing with seed generation. (The program is fairly large.) – Mathhead200 Jul 09 '14 at 19:53
  • I think I misunderstood your answer when I responded before. It still doesn't address the issue of multiple machines (or instances of the program running concurrently), but the sequences _would_ be independent and it wouldn't be hard to implement. However, there is a slight change that the PRNG seeded could send repeat seeds. – Mathhead200 Jul 11 '14 at 20:21
1

From the comments I understand you want to run several instances of the algorithm, one instance per thread. And given that the seed for each instance will be generated pretty much at the same time, you want to ensure that these seeds are different. If that is indeed what you are trying to solve, then your genSeed function will not necessarily guarantee that.

In my opinion, what you need is a parallelisable random number generator (RNG). What this means, is that you only need one RNG which you instantiate with only one seed (which you can generate with your genSeed) and then the sequence of random numbers that would normally be gerenated in a sequential environment is split in X non-overlapping sequences; where X is the number of threads. There is a very good library which provides these type of RNGs in C++, follows the C++ standard for RNGs, and is called TRNG(http://numbercrunch.de/trng).

Here is a little more information. There are two ways you can achieve non-overlapping sequences per thread. Let's assume that the sequence of random numbers from a single RNG is r = {r(1), r(2), r(3),...} and you have only two threads. If you know in advance how many random numbers you will need per thread, say M, you can give the first M of the r sequence to the first thread, ie {r(1), r(2),..., r(M)}, and the second M to the second thread, ie {r(M+1), r(M+2),...r(2M)}. This technique is called blocksplitting since you split the sequence in two consecutive blocks.

The second way is to create the sequence for the first thread as {r(1), r(3), r(5), ...} and for the second thread as {r(2), r(4), r(6),...}, which has the advantage that you do not need to know in advance how many random numbers you will need per thread. This is called leapfroging.

Note that both methods guarantee that the sequences per thread are indeed non-overlapping. The link I posted above has many examples and the library itself is extremely easy to use. I hope my post helps.

linuxfever
  • 3,162
  • 1
  • 13
  • 34
  • Thanks, but we also run this program on multiple machines as well, and that would mean the random numbers must be generated in a separate program and sent across a network (and that's assuming the computer stay networked as they are now.) – Mathhead200 Jul 11 '14 at 19:59
0

The POSIX function gettimeofday(2) gives the time with microsecond precision.

The POSIX thread function gettid(2) returns the ID number of the current thread.

You should be able to combine the time in seconds since the epoch (which you are already using), the time in microseconds, and the thread ID to get a seed which is always unique on one machine.

If you also need it to be unique across multiple machines, you could consider also getting the hostname, the IP address, or the MAC address.

I would guess that 32 bits is probably enough, since there are over 4 billion unique seeds available. Unless you are running billions of processes, which doesn't seem likely, you should be alright without going to 64 bit seeds.

jsw
  • 115
  • 8
  • Are POSIX functions available on Windows, because I don't want the program to become OS dependent? Also, we are not running four+ billion processes, no; however, you don't need to run four billion processes to get a repeating 32-bit seed (see the [Birthday Problem](http://en.wikipedia.org/wiki/Birthday_problem).) – Mathhead200 Jul 11 '14 at 20:28
  • So, using an approximation to the birthday problem probability, it looks like you have about a 99% chance of having zero collisions when using 10k processes with a 32 bit seed. But, having a collision only matters if the colliding processes are using the same parameters. If you have different run parameters, then a collision will have no impact. I'm just trying to save you unnecessary trouble (unless it actually is necessary). – jsw Jul 11 '14 at 20:53
  • 1
    POSIX is supported on Windows via Cygwin, but do you have users on Windows yet? It's better to have a program that works but is OS dependent than to fuss over OS dependence and have no program at all. If you find that you are hindering adoption, then you can come back to this problem, and even recruit additional developers to help. This is similar to the idea of avoiding premature optimization. Ultimately, you will probably need OS-dependent code with switches to select the appropriate method. Also, if OS-independence is important, add it to the original question (and bounty)! – jsw Jul 11 '14 at 20:56
  • The program is currently only running on Windows, and I don't have the capability to "recruit" anyone else to help (I wish I did; I've tried.) The program might end up running on a cloud or grid system, or even a super computer at some point, and I don't want to add any dependencies that I can avoid. Thanks for telling me about Cygwin though! – Mathhead200 Jul 12 '14 at 23:56
  • You are correct that a different seed is on;y really important if there are identical parameters (which isn't often). I was just clarifying your post. Also, I might as well use 64 bits if I have them. – Mathhead200 Jul 12 '14 at 23:58
  • All true. Might still be worth adding the OS information to the original question. – jsw Jul 15 '14 at 01:32