It has been a long standing wish that OMNeT++ supports modern RNGs such as Mersenne Twister. This has been finally implemented, and much more. Read on… <P>The new random number architecture, available from 3.0 alpha 9, offers the following improvements:</P>

  1. choice of 3 random number generator (RNG) algorithms, and you can easily plug in more
  2. number of RNGs (or random streams) is a configuration option, with no upper limit
  3. RNG mapping: you can make one group of modules use one RNG (or set of RNGs), and another group of modules a different RNG
  4. object-oriented as well as C-style access to RNGs
  5. reasonably backward compatible with older OMNeT++ versions
  6. </OL>

    Let's see how this is achieved.

    Selecting the RNG algorithm and the number of RNGs

    Knowing existing OMNeT++ plug-in interfaces (omnetpp.ini entries configuration-class=, outputvectormanager-class=, outputscalarmanager-class=, snapshotmanager-class=, scheduler-class=, etc.), you may have guessed that when selecting an RNG algorithm, you really choose a C++ class.

    The rng-class= omnetpp.ini entry has to be in the [General] section, and it can currently take the following values: "cMersenneTwister", "cLCG32", and (if Akaroa support is enabled) "cAkaroaRNG". cMersenneTwister, which is also the default, selects the MT19937 RNG which has the incredible cycle length of 2^19937-1 (the actual class wraps Rick Wagner's MersenneTwister.h). cLCG32 wraps the original OMNeT++ RNG with the cycle length 2^31-2.

    By default, only one RNG is created. To increase number of RNGs, you have to add a num-rngs=n config entry -- there is no upper limit. The following example makes five cLCG32 RNGs (numbered 0 thru 4) available to the simulation.

    [General]
    rng-class="cLCG32"   # default is "cMersenneTwister"
    num-rngs=5

    Akaroa has only one RNG, so setting num-rngs >= 2 with cAkaroaRNG will cause an error.

    If you want to create a new RNG class, you have to subclass cRNG, and let OMNeT++ know about the new class with the Register_Class() macro. That's all -- you can activate it with rng-class="MyRNGClass".

    Seeding the RNGs

    Seeding is the procedure of setting the initial states of the RNGs, so that they produce a new (or the same, as we wish) stream of random numbers. It is generally a good idea that streams from different RNGs don't overlap, because that may introduce unwanted correlation into the simulation, and falsify the results.

    It is up to the RNG class to implement support for seeding. (On initialization, the RNG class gets access to the full configuration and can read whichever entries it is interested in.) cMersenneTwister reads the seed-0-mt=, seed-1-mt=, etc entries to initialize RNG 0, RNG 1 etc. with 32-bit values. cLCG32 uses seed-0-lcg32=, seed-1-lcg32, etc. The config entry names are different to make it easier to switch between RNGs by allowing "-mt and "-lcg32" entries coexist in the same omnetpp.ini.

    Example:

    [General]
    rng-class="cLCG32"
    num-rngs=2
    seed-0-lcg32 = 3454326
    seed-1-lcg32 = 9764754

    Seed sets can be specified globally (in the [General] section) or per-run. The seedtool program already present in earlier OMNeT++ released can continue to be used for generating seeds for cLCG32. cMersenneTwister has such a long cycle that there's no need for seed generation -- arbitrary seed values will do, because chances are very-very small that any two seeds produce overlapping streams. cAkaroaRNG is fully under Akaroa's control, and it doesn't need to (and cannot) be seeded from OMNeT++.

    Automatic seeding

    If you don't explicitly specify seeds in omnetpp.ini, RNGs will get automatic seeds. For cLCG32 OMNeT++ has a table of 256 seeds, spaced about 8 million values apart, covering the full 2^31-2 long sequence. There was a bug in earlier OMNeT++ versions that the first network set up in the simulation got the first set of automatic seeds, the second network (or the same network after the first restart) got the second set of automatic seeds, etc. This has changed: the assigned seeds depend on the Run number and the number of RNGs alone, and not how many times the model was restarted. Actually: N = (Run# * num-rngs + rng-index) is computed, and cLCG32 assigns the Nth seed in the table. cMersenneTwister simply uses N as seed.

    RNG mapping

    Variance reduction techniques require control over which part (or function) of the simulation model uses which RNG. However, components (simple and compound modules) are usually not written with that kind of flexibility in mind, but just use RNG-0. For example, a NED parameter packetLength=truncnormal(1000,200) uses RNG-0 -- one would have to change it to truncnormal(1000,200,1) to switch it over to RNG-1, but in a large model that's usually just not feasible (too many places to change). This makes it necessary that the simulation framework provides this kind of functionality.

    In OMNeT++, using num-rngs=N you create N physical RNGs. Modules refer to RNGs by numbers as well, these numbers are not immediately the "physical" RNG numbers: mapping step has been introduced between the two. You can specify that if a given module refers to e.g. RNG-0, which physical RNG it should map to. The omnetpp.ini syntax is the following:

    [General]
    num-rngs=4

    **.host[*].trafgen.rng-0 = 0
    **.host[*].trafgen.rng-1 = 1
    **.mac[*].rng-0 = 2
    **.noisychan[*].rng-0 = 3

    This way all traffic generators will use RNG 0 and 1, MACs will use RNG 2 (e.g. for exponential backoff), and noisy channel models will use RNG 3 to introduce packet loss and bit errors.

    Access from C++

    The old intrand(r), dblrand(), genk_intrand(rng,r)genk_dblrand(rng) functions continue to work, only they go through the RNG mapping and use the new RNGs. The random variate functions (exponential(), normal(), etc.) also work.

    In addition to this classic C-style API, you can access RNGs also via the new cRNG interface which gives you more details. cModule's rng(k) function returns a cRNG* pointer to the given RNG. RNG numbers accepted by all the above functions are module-local numbers, so they can be arbitrarily mapped to physical RNGs from omnetpp.ini. By default they use (module-local) RNG 0 which by default maps to physical RNG 0.

    The physical RNGs themselves can be accessed -- bypassing the mapping -- by ev.rng(k). ev.numRNGs() returns the number of RNGs as configured in omnetpp.ini. 

    Functions for explicitly getting/setting seeds (opp_randseed(), etc) have been removed; intRand() (without arguments) has also been removed because different RNG algorithms have different "native" ranges: (0..2^32-1, 1..2^31-2, etc). So if you referred to seeds from the C++ code (which was probably a Bad Thing to do), you'll get undeclared functions during compilation, and you'll need to rethink your code. Luckily I haven't seen this practice in any simulation model so far.

    What do I need to change in my simulation model?

    Probably only omnetpp.ini. If you didn't care much about RNGs and seeding at all, you just comment out the obsolete random-seed=, gen0-seed=, gen1-seed= etc entries from your omnetpp.ini -- and that's all.

    If your experiments rely on the old RNG and explicit seeds, insert rng-class="cLCG32" (and if needed, num-rngs=N) in omnetpp.ini, and change the random-seed=, gen0-seed=, gen1-seed= etc entries to seed-0-lcg32=, seed-1-lcg32=.

    Questions, comments are welcome!