简体   繁体   中英

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

I want to create many instances of a class, and want them to use the boost random number generator to make a normally distributed jump with a user defined mean. I have been reading from some sources they say you don't want to reseed the number generator like here . Ideally I wanted a global generator and that each instance of the individual class has the ability to change the mean and generate a random number that is not the same for all instances. I am struggling to implement this I have a global normal class, but the seed is the same for each instance of the class.

// 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>

/**
 * 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
 */
struct random_generator : boost::mt11213b {
    random_generator(void){
        seed(static_cast<unsigned int>(std::time(0)));
    }
} random_generator;


template<
    class Type
> struct Distribution {
    Type distribution;
    boost::variate_generator<decltype(random_generator),Type> variate_generator;

    template<class... Args>
    Distribution(Args... args):
        variate_generator(random_generator,Type(args...)) {}

    double random(void) {
        return variate_generator();
    }
};
typedef Distribution< boost::normal_distribution<> > Normal;

using namespace std;
// global normal random number generator
Normal normal_random_generator;

// 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_random_generator = {mu, sigma_};
            distance_ += normal_random_generator.random();
        }

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

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


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

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

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

    system("PAUSE");
    return 0;
}

This is the output you get from compiling the above code.

!!!Begin!!!
starting values: individual a = 0.4 individual b = 0.4
A   B
41.8024 41.8024
18509.2 18509.2
24843.6 24843.6
51344   51344
70513.4 70513.4
86237.8 86237.8
97716.2 97716.2
127075  127075
154037  154037
178501  178501
finished

I've tried to come up with an example that does what I think you want to achieve. I hope you don't mind that I got rid of boost as I don't see a reason to use it here.

Hope this helps:

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

// Class Individual
class Individual
{
public:
  // an accessor to pass information back
  void move_bias_random_walk( double mu, double sigma = 0.4 )
  {
    distance_ += std::normal_distribution<double>{mu, sigma}( myEngine );
  }

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

private:
  // containers
  double distance_ = 0.4;
  static std::mt19937 myEngine;
};

// initialize static random engine to be shared by all instances of the Inidvidual class   
auto Individual::myEngine = std::mt19937( std::time( 0 ) );

int main()
{
  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;

  // let's not use rand()
  std::default_random_engine eng{1337};
  std::uniform_real_distribution<double> uniformMean{-10,10};

  for ( auto i = 1; i <= 10; ++i ) {
    double mean = uniformMean(eng);
    individual_a.move_bias_random_walk( mean );
    individual_b.move_bias_random_walk( mean );
    std::cout << individual_a.get_distance() << " " << individual_b.get_distance() << std::endl;
  }

  std::cout << "finished" << std::endl;
  return 0;
}

It prints:

!!!Begin!!!
starting values: individual a = 0.4 individual b = 0.4
A   B
8.01456 7.68829
2.53383 1.19675
7.06496 5.74414
9.60985 9.04333
12.4008 13.4647
11.2468 13.4128
6.02199 8.24547
0.361428 2.85905
-3.28938 -1.59109
-5.99163 -4.37436
finished

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM