Reputation: 711
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
Upvotes: 1
Views: 1487
Reputation: 394054
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());
}
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
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.
// 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
The following is exactly equivalent but way more efficient:
#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
Engine
instance as you desireUpvotes: 2
Reputation: 766
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
Upvotes: 3