Boost Random Number Generator

Boost random number generator

This code is adapted from the boost manual at http://www.boost.org/doc/libs/1_42_0/libs/random/index.html:

#include <iostream>
#include "boost/random.hpp"
#include "boost/generator_iterator.hpp"
using namespace std;

int main() {
typedef boost::mt19937 RNGType;
RNGType rng;
boost::uniform_int<> one_to_six( 1, 6 );
boost::variate_generator< RNGType, boost::uniform_int<> >
dice(rng, one_to_six);
for ( int i = 0; i < 6; i++ ) {
int n = dice();
cout << n << endl;
}
}

To explain the bits:

  • mt19937 is the mersenne twister generator,which generates the raw random numbers. A typedef is used here so you can easily change random number generator type.

  • rng is an instance of the twister generator.

  • one_to_six is an instance of a distribution. This specifies the numbers we want to generate and the distribution they follow. Here we want 1 to 6, distributed evenly.

  • dice is the thing that takes the raw numbers and the distribution, and creates for us the numbers we actually want.

  • dice() is a call to operator() for the dice object, which gets the next random number following the distribution, simulating a random six-sided dice throw.

As it stands, this code produces the same sequence of dice throws each time. You can randomise the generator in its constructor:

 RNGType rng( time(0) );   

or by using its seed() member.

How do I use Boost Random

the random number for my case must be double not just integer...

So, you use a real number distribution.


I'm not this kind of "getting started" is the best fit for StackOverflow, but I'll give you this quick hints:

In your Ubuntu virtual box:

sudo apt-get install libboost-all-dev
mkdir -pv ~/myproject
cd ~/myproject

Create a file using your favourite editor. If you have none, gedit main.cpp or nano main.cpp is a start:

#include <boost/random.hpp>
#include <iostream>

int main() {
boost::random::mt19937 rng;
boost::random::uniform_real_distribution<double> gen(0.0, 1.0);
for (int i = 0; i < 10; ++i) {
std::cout << gen(rng) << "\n";
}
}

Now compile it using

g++ -O2 -Wall -Wextra -pedantic main.cpp -o demo

The program is now ready to run: Live On Coliru

./demo

Printing

0.814724
0.135477
0.905792
0.835009
0.126987
0.968868
0.913376
0.221034
0.632359
0.308167

Seeding && Non-Header Only Libraries

The above works because the Boost Random library is mostly header only. What if you wanted to use the random_device implementation to seed the random generator?

Live On Coliru

#include <boost/random.hpp>
#include <boost/random/random_device.hpp>
#include <iostream>

int main() {
boost::random::random_device seeder;
boost::random::mt19937 rng(seeder());
boost::random::uniform_real_distribution<double> gen(0.0, 1.0);
for (int i = 0; i < 10; ++i) {
std::cout << gen(rng) << "\n";
}
}

Now you'll have to link as well: Compiling with

g++ -O2 -Wall -Wextra -pedantic main.cpp -o demo -lboost_random

Now the output will be different each run.

BONUS: Standard Library instead of Boost

You don't need Boost here at all:

Live On Coliru

#include <random>
#include <iostream>

int main() {
std::random_device seeder;
std::mt19937 rng(seeder());
std::uniform_real_distribution<double> gen(0.0, 1.0);
for (int i = 0; i < 10; ++i) {
std::cout << gen(rng) << "\n";
}
}

Compile with

g++ -std=c++11 -O2 -Wall -Wextra -pedantic main.cpp -o demo

And run it again with ./demo

BONUS

Showing a whole gamut of distributions that have mean=0 and stddev=1:

Live On Coliru

#include <random>
#include <iostream>
#include <iomanip>
#include <chrono>
#include <boost/serialization/array_wrapper.hpp>
#include <boost/accumulators/accumulators.hpp>
#include <boost/accumulators/statistics.hpp>

namespace ba = boost::accumulators;
using Accum = ba::accumulator_set<double, ba::stats<ba::tag::variance, ba::tag::mean> >;
using Clock = std::chrono::high_resolution_clock;
using namespace std::chrono_literals;

static double identity(double d) { return d; }

template <typename Prng, typename Dist, typename F = double(double), size_t N = (1ull << 22)>
void test(Prng& rng, Dist dist, F f = &identity) {
Accum accum;

auto s = Clock::now();
for (size_t i = 0; i<N; ++i)
accum(f(dist(rng)));

std::cout
<< std::setw(34) << typeid(Dist).name()
<< ":\t" << ba::mean(accum)
<< " stddev: " << sqrt(ba::variance(accum))
<< " N=" << N
<< " in " << ((Clock::now()-s)/1.s) << "s"
<< std::endl;
}

int main() {
std::mt19937 rng(std::random_device{}());

auto shift = [](double shift) { return [=](double v) { return v + shift; }; };
auto scale = [](double scale) { return [=](double v) { return v * scale; }; };

std::cout << std::fixed << std::showpos;
test(rng, std::uniform_real_distribution<double>(-sqrt(3), sqrt(3)));
test(rng, std::weibull_distribution<double>(), shift(-1));
test(rng, std::exponential_distribution<double>(), shift(-1));
test(rng, std::normal_distribution<double>());
test(rng, std::lognormal_distribution<double>(0, log(0.5)), shift(-exp(pow(log(0.5),2)/2)));
test(rng, std::chi_squared_distribution<double>(0.5), shift(-0.5));
{
auto sigma = sqrt(6)/M_PI;
static constexpr double ec = 0.57721566490153286060;
test(rng, std::extreme_value_distribution<double>(-sigma*ec, sigma));
}
test(rng, std::fisher_f_distribution<double>(48, 8), shift(-(8.0/6.0)));
test(rng, std::student_t_distribution<double>(4), scale(sqrt(0.5)));
test(rng, std::student_t_distribution<double>(4), scale(sqrt(0.5)));
}

Prints

  St25uniform_real_distributionIdE: +0.000375 stddev: +1.000056 N=4194304 in +0.169681s
St20weibull_distributionIdE: +0.001030 stddev: +1.000518 N=4194304 in +0.385036s
St24exponential_distributionIdE: -0.000360 stddev: +1.000343 N=4194304 in +0.389443s
St19normal_distributionIdE: -0.000133 stddev: +1.000330 N=4194304 in +0.390235s
St22lognormal_distributionIdE: +0.000887 stddev: +1.000372 N=4194304 in +0.521975s
St24chi_squared_distributionIdE: -0.000092 stddev: +0.999695 N=4194304 in +1.233835s
St26extreme_value_distributionIdE: -0.000381 stddev: +1.000242 N=4194304 in +0.611973s
St21fisher_f_distributionIdE: -0.000073 stddev: +1.001588 N=4194304 in +1.326189s
St22student_t_distributionIdE: +0.000957 stddev: +0.998087 N=4194304 in +1.080468s
St22student_t_distributionIdE: +0.000677 stddev: +0.998786 N=4194304 in +1.079066s

C++ boost random number generator set seed for multiple instances

Like Christoph commented, if you copy the state of the generator engine, you'll have two engines with the same state.

So seed the engines after the copy:

template<class... Args>
Distribution(Args... args):
variate_generator(random_generator,Type(args...)) {
boost::random::random_device dev;
variate_generator.engine().seed(dev);
}

Note how seeding from random_device is vastly preferable. This makes sure the seed is itself random and also that the entire state of the engine is seeded.

If you don't want to link to Boost Random, you can use a single seed value again:

template<class... Args>
Distribution(Args... args):
variate_generator(random_generator,Type(args...)) {
std::random_device dev;
variate_generator.engine().seed(dev());
}

Other Issues

When you do

normal_random_generator = {mu, sigma_};

you're REPLACING your global Distribution instance, and setting mu to the value you get from main. Since you (ab)use rand() there, mu will just be some completely un-random and largish value. On my system it's always

1804289383
846930886
1681692777
1714636915
1957747793
424238335
719885386
1649760492
596516649
1189641421

Your distribution's sigma is pretty small in comparison, therefore your generated values will be close to the original, and the scientific formatting of the number will hide any difference:

!!!Begin!!!
starting values: individual a = 0.4 individual b = 0.4
A B
1.80429e+09 1.80429e+09
2.65122e+09 2.65122e+09
4.33291e+09 4.33291e+09
6.04755e+09 6.04755e+09
8.0053e+09 8.0053e+09
8.42954e+09 8.42954e+09
9.14942e+09 9.14942e+09
1.07992e+10 1.07992e+10
1.13957e+10 1.13957e+10
1.25853e+10 1.25853e+10
finished

That looks as if both columns have the same values. However, they're basically just the output of rand() with minor variation. Adding

std::cout << std::fixed;

Shows that there ARE differences:

!!!Begin!!!
starting values: individual a = 0.4 individual b = 0.4
A B
1804289383.532134 1804289383.306165
2651220269.054946 2651220269.827112
4332913046.416999 4332913046.791281
6047549960.973747 6047549961.979666
8005297753.938927 8005297755.381466
8429536088.122741 8429536090.737263
9149421474.458202 9149421477.268963
10799181966.514246 10799181969.109875
11395698614.754076 11395698617.892900
12585340035.563337 12585340038.882833
finished

All in all, I'd suggest

  • not using rand() and/or picking a more suitable range for mean
  • Also I suggest never using global variables. With the fact that you create a new instance of Normal every time here:

        normal_random_generator = {mu, sigma_};

    I don't see what value there could possibly be in overwriting a global variable with that instance. It just makes it less efficient. So, this is strictly equivalent and more efficient:

    void move_bias_random_walk(double mu) {
    Normal nrg {mu, sigma_};
    distance_ += nrg.random();
    }
  • Understand your distribution's Sigma, so you can predict the variance of the numbers to expect.

Fixed Code #1

Live On Coliru

// C/C++ standard library
#include <iostream>
#include <cstdlib>
#include <ctime>

#include <boost/random/mersenne_twister.hpp>
#include <boost/random/variate_generator.hpp>
#include <boost/random/lognormal_distribution.hpp>
#include <boost/random/random_device.hpp>

/**
* The mt11213b generator is fast and has a reasonable cycle length
* See http://www.boost.org/doc/libs/1_60_0/doc/html/boost_random/reference.html#boost_random.reference.generators
*/
typedef boost::mt11213b Engine;
boost::random::random_device random_device;

template<
class Type
> struct Distribution {
boost::variate_generator<Engine, Type> variate_generator;

template<class... Args>
Distribution(Args... args):
variate_generator(Engine(random_device()), Type(args...)) {
//variate_generator.engine().seed(random_device);
//std::cout << "ctor test: " << variate_generator.engine()() << "\n";
}

double random(void) {
double v = variate_generator();
//std::cout << "debug: " << v << "\n";
return v;
}
};

typedef Distribution< boost::normal_distribution<> > Normal;

// Class Individual
class Individual {
public:
Individual() { } // constructor initialise value
virtual ~Individual() = default;

// an accessor to pass information back
void move_bias_random_walk(double mu) {
Normal nrg {mu, sigma_};
distance_ += nrg.random();
}

// An accessor for the distance object
double get_distance() {
return distance_;
}

private:
//containers
double distance_ = 0.4;
double sigma_ = 0.4;
};

int main() {
std::cout << std::fixed;
std::cout << "!!!Begin!!!" << std::endl;
// Initialise two individuals in this case but there could be thousands
Individual individual_a;
Individual individual_b;

std::cout << "starting values: individual a = " << individual_a.get_distance() << " individual b = " << individual_b.get_distance() << std::endl;
// Do 10 jumps with the same mean for each individual and see where they end up each time

std::cout << "A\tB" << std::endl;
for (auto i = 1; i <= 10; ++i) {
double mean = rand()%10;
//std::cout << "mean: " << mean << "\n";
individual_a.move_bias_random_walk(mean);
individual_b.move_bias_random_walk(mean);
std::cout << individual_a.get_distance() << "\t" << individual_b.get_distance() << std::endl;
}
std::cout << "finished" << std::endl;
}

Prints

!!!Begin!!!
starting values: individual a = 0.400000 individual b = 0.400000
A B
3.186589 3.754065
9.341219 8.984621
17.078740 16.054461
21.787808 21.412336
24.896861 24.272279
29.801920 29.090233
36.134987 35.568845
38.228595 37.365732
46.833353 46.410176
47.573564 47.194575
finished

Simplifying: Demo #2

The following is exactly equivalent but way more efficient:

Live On Coliru

#include <boost/random/mersenne_twister.hpp>
#include <boost/random/normal_distribution.hpp>
#include <boost/random/random_device.hpp>
#include <iostream>

/**
* The mt11213b generator is fast and has a reasonable cycle length
* See http://www.boost.org/doc/libs/1_60_0/doc/html/boost_random/reference.html#boost_random.reference.generators
*/
typedef boost::mt11213b Engine;

template <typename Distribution>
class Individual {
public:
Individual(Engine& engine) : engine_(engine) { }

// an accessor to pass information back
void move_bias_random_walk(double mu) {
Distribution dist { mu, sigma_ };
distance_ += dist(engine_);
}

// An accessor for the distance object
double get_distance() {
return distance_;
}

private:
Engine& engine_;
//containers
double distance_ = 0.4;
double sigma_ = 0.4;
};

int main() {
boost::random::random_device device;
Engine engine(device);

std::cout << std::fixed;
std::cout << "!!!Begin!!!" << std::endl;

// Initialise two individuals in this case but there could be thousands
Individual<boost::normal_distribution<> > individual_a(engine);
Individual<boost::normal_distribution<> > individual_b(engine);

std::cout << "starting values: individual a = " << individual_a.get_distance() << " individual b = " << individual_b.get_distance() << std::endl;
// Do 10 jumps with the same mean for each individual and see where they end up each time

std::cout << "A\tB" << std::endl;
for (auto i = 1; i <= 10; ++i) {
double mean = rand()%10;
individual_a.move_bias_random_walk(mean);
individual_b.move_bias_random_walk(mean);
std::cout << individual_a.get_distance() << "\t" << individual_b.get_distance() << std::endl;
}
std::cout << "finished" << std::endl;
}

Note

  • it shares the Engine instance as you desire
  • it does not use global variables (or, worse, re-assign them!)
  • otherwise as exactly the same behaviour but in much less code

Using boost to generate random numbers between 1 and 9999

Did you try googling for "boost random number" first? Here's the relevant part of their documentation generating boost random numbers in a range

You want something like this:

#include <time.h>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_int_distribution.hpp>
std::time(0) gen;

int random_number(start, end) {
boost::random::uniform_int_distribution<> dist(start, end);
return dist(gen);
}

edit: this related question probably will answer most of your questions: why does boost::random return the same number each time?

Encapsulated Random number generator in C++-11 using boost

boost::variate_generator takes its constructor arguments by value, so when you pass it a default constructed mt19937 in the mem-initializer list it makes a copy of it. Seeding the engine within the body of the constructor will have no effect on that copy. To fix this, change the template argument type to a reference.

typedef boost::variate_generator<ENG&,DIST> GEN;
// ^

Next, I don't think you want a normal_distribution, its constructor arguments are not the minimum and maximum of the range of values produced by the distribution, but the mean and standard deviation of the distribution.

The distribution you're probably looking for is uniform_real_distribution.

typedef boost::random::uniform_real_distribution<> DIST;

After making these fixes, your code should behave as you want it to.

Live demo


With a C++11 compiler you don't even need Boost to do this, the <random> header provides all the components you need. The only significant difference is the lack of a variate_generator class, instead you invoke the distribution object using the engine as the argument when you want to generate a random number.

#include <random>

typedef std::mt19937 ENG; // Mersenne Twister
typedef std::uniform_real_distribution<> DIST; // Uniform Distribution

class RNG {

private:
ENG eng;
DIST dist;

public:
DIST::result_type gen() { return dist(eng); }
RNG(double min,double max,int seed)
: dist(min,max)
{eng.seed(seed); }
};

Live demo

Random numbers, C++11 vs Boost

That’s pretty scary.

Let’s have a look:

boost::bernoulli_distribution<>

if(_p == RealType(0))
return false;
else
return RealType(eng()-(eng.min)()) <= _p * RealType((eng.max)()-(eng.min)());

std::bernoulli_distribution

__detail::_Adaptor<_UniformRandomNumberGenerator, double> __aurng(__urng);
if ((__aurng() - __aurng.min()) < __p.p() * (__aurng.max() - __aurng.min()))
return true;
return false;

Both versions invoke the engine and check if the output lies in a portion of the range of values proportional to the given probability.

The big difference is, that the gcc version calls the functions of a helper class _Adaptor.

This class’ min and max functions return 0 and 1 respectively and operator() then calls std::generate_canonical with the given URNG to obtain a value between 0 and 1.

std::generate_canonical is a 20 line function with a loop – which will never iteratate more than once in this case, but it adds complexity.

Apart from that, boost uses the param_type only in the constructor of the distribution, but then saves _p as a double member, whereas gcc has a param_type member and has to “get” the value of it.

This all comes together and the compiler fails in optimizing.
Clang chokes even more on it.

If you hammer hard enough you can even get std::mt19937 and boost::mt19937 en par for gcc.

It would be nice to test libc++ too, maybe i’ll add that later.


tested versions: boost 1.55.0, libstdc++ headers of gcc 4.8.2

line numbers on request^^

Incorporating boost random number generator as a class variable

boost::variate_generator has no default constructor, so you need to use your constructor's initialization list:

FR::FR()
: dist(0,1), gen(eng,dist)
{}

Consistent random number generation accross platforms with boost::random

it seems boost::random does not guarantee that you get the same sequence of numbers for certain seeds with different versions boost.

E.g. in version 1.56 they have changed the algorithm to generate normal distributed random numbers from the Box-Muller method to the Ziggurat method:

https://github.com/boostorg/random/commit/f0ec97ba36c05ef00f2d29dcf66094e3f4abdcde

This method is faster but also produces different number sequences.

Similar changes have probably been done to the other distributions. The uniform distribution still produces the same result as that is typically the output of the base rng which is a mersenne twister 19937 by default.



Related Topics



Leave a reply



Submit