of course, living in a state of sin." - John Von Neumann
Despite of the fact that the quote above is still highly relevant, we mostly get the job done well enough using those pseudo-random generators for our Monte Carlo stuff. So, this time I wanted to present a wrapper for simulating (sinful) uniform random numbers, which are then mapped to a chosen probability distribution. The main goal was to construct a lightweight design, using C++11 uniform generators, probability distributions as well as some other extremely useful tools (function, lambda) for the task.
TEMPLATE DIET
Instead of implementing any class hierarchies, I tried to keep this design as lightweighted as possible by using template class having only two template data types to be defined by a client : Generator (uniform random generator) and Distribution (probability distribution for transforming uniformly distributed random numbers). In a nutshell, a client may use any random number engine available in C++11, for creating desired amount of uniform random numbers. Moreover, a client may use default probability distribution (Standard Normal) for transforming created uniform random numbers or additionally, use any desired probability distribution available in C++11. Available uniform random engines and probability distributions have been nicely covered in here.
ULTIMATE SIN
It should be a bit easier to sell something, if the final product is nicely presented. Random numbers processed by example program (presented later) for four different random generator implementations are presented in Excel screenshot below.
HEADER FILE
Random generator template class is presented below. Note, that this implementation does not require any auxiliary libraries, since all the required stuff is already in C++11. For usability reasons, I decided to keep all the actual method implementations in header file (RandomGenerator.h).
#pragma once #include <algorithm> #include <chrono> #include <cmath> #include <functional> #include <iostream> #include <math.h> #include <memory> #include <random> #include <string> #include <vector> // namespace MikeJuniperhillRandomGeneratorTemplate { template <typename Generator = std::mt19937, typename Distribution = std::normal_distribution<double>> /// <summary> /// Template class for creating random number paths using /// Mersenne Twister as default uniform random generator and /// Standard Normal (0.0, 1.0) as default probability distribution. /// </summary> class RandomGenerator { public: /// <summary> /// Constructor for using default probability distribution /// for transforming uniform random numbers. /// </summary> RandomGenerator(const std::function<unsigned long(void)>& seeder) : seeder(seeder) { // construct lambda method for processing standard normal random number randomGenerator = [this](double x)-> double { x = distribution(uniformGenerator); return x; }; // seed uniform random generator with a client-given algorithm uniformGenerator.seed(seeder()); } /// <summary> /// Constructor for using client-given probability distribution /// for transforming uniform random numbers. /// </summary> RandomGenerator(const std::function<unsigned long(void)>& seeder, const Distribution& distribution) // constructor delegation for initializing other required member data : RandomGenerator(seeder) { // assign client-given probability distribution this->distribution = distribution; } /// <summary> /// Functor filling vector with random numbers mapped to chosen probability distribution. /// </summary> void operator()(std::vector<double>& v) { std::transform(v.begin(), v.end(), v.begin(), randomGenerator); } private: std::function<unsigned long(void)> seeder; std::function<double(double)> randomGenerator; Generator uniformGenerator; Distribution distribution; }; }
TESTER FILE
Example program (tester.cpp) for testing random generator functionality is presented below. The process of generating (pseudo) random numbers from a probability distribution is easy and straightforward with this template wrapper class.
#include "RandomGenerator.h" namespace MJ = MikeJuniperhillRandomGeneratorTemplate; // // printer for a random path void Print(const std::string& message, const std::vector<double>& v) { std::cout << message << std::endl; for (auto& element : v) std::cout << element << std::endl; std::cout << std::endl; } // int main() { // construct lambda method for seeding uniform random generator std::function<unsigned long(void)> seeder = [](void) -> unsigned long { return static_cast<unsigned long> (std::chrono::steady_clock::now().time_since_epoch().count()); }; // // create vector for a path to be filled with 20 random numbers // from desired probability distribution, using desired uniform random generator int nSteps = 20; std::vector<double> path(nSteps); // // path generator : mersenne twister, standard normal MJ::RandomGenerator<> gen_1(seeder); gen_1(path); Print("mt19937 : x~N(0.0, 1.0)", path); // // path generator : minst_rand, standard normal MJ::RandomGenerator<std::minstd_rand> gen_2(seeder); gen_2(path); Print("minstd_rand : x~N(0.0, 1.0)", path); // // path generator : mersenne twister, normal(112.5, 1984.0) std::normal_distribution<double> nd(112.5, 1984.0); MJ::RandomGenerator<> gen_3(seeder, nd); gen_3(path); Print("mt19937 : x~N(112.5, 1984.0)", path); // // path generator : minstd_rand, exponential(3.14) std::exponential_distribution<double> ed(3.14); MJ::RandomGenerator<std::minstd_rand, std::exponential_distribution<double>> gen_4(seeder, ed); gen_4(path); Print("minstd_rand : x~Exp(3.14)", path); // return 0; }
Finally, thanks again for reading this blog.
-Mike
No comments:
Post a Comment