代码之家  ›  专栏  ›  技术社区  ›  Cyrillm_44

用于多个实例的C++ Boost随机数生成器集合

  •  1
  • Cyrillm_44  · 技术社区  · 8 年前

    我想创建一个类的许多实例,并希望它们使用boost随机数生成器以用户定义的平均值进行正态分布跳转。我从一些来源读到他们说你不想像 here . 理想情况下,我需要一个全局生成器,并且每个类的实例都有能力更改平均值,并生成一个随机数,这对所有实例都不一样。我正在努力实现这一点,我有一个全局标准类,但该类的每个实例的种子都是相同的。

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

    这是编译上述代码得到的输出。

    !!!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
    
    2 回复  |  直到 8 年前
        1
  •  2
  •   sehe    8 年前

    正如christoph所评论的,如果复制生成器引擎的状态,将有两个状态相同的引擎。

    因此,在复制之后为引擎设定种子:

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

    注意种子是如何从 random_device 是非常可取的。这可以确保种子本身是随机的,并且引擎的整个状态都是种子。

    如果不想链接到Boost Random,可以再次使用单个种子值:

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

    其他问题

    当你这样做的时候

    normal_random_generator = {mu, sigma_};
    

    你要替换你的全球 Distribution 实例和设置 mu 你从梅因得到的价值。因为你(AB)使用 rand() 在那里, 穆 只是一些完全不随机的较大值。在我的系统里

    1804289383
    846930886
    1681692777
    1714636915
    1957747793
    424238335
    719885386
    1649760492
    596516649
    1189641421
    

    与此相比,您的分布的sigma很小,因此生成的值将接近原始值,数字的科学格式将隐藏任何差异:

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

    看起来两列的值都一样。不过,它们基本上只是 RAND() 变化很小。添加

    std::cout << std::fixed;
    

    表明存在差异:

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

    总之,我建议

    • 不使用 RAND() 和/或选择更合适的范围 mean
    • 我也建议 从未 使用全局变量。创建一个新的 Normal 每次来这里:

          normal_random_generator = {mu, sigma_};
      

      我不知道用那个实例重写全局变量可能有什么价值。只会降低效率。因此,这是严格等价和更有效的:

      void move_bias_random_walk(double mu) {
          Normal nrg {mu, sigma_};
          distance_ += nrg.random();
      }
      
    • 了解你的分布的西格玛,这样你就可以预测预期的数字的方差。

    固定代码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;
    }
    

    印刷品

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

    简化:演示2

    以下内容完全相同,但效率更高:

    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;
    }
    

    音符

    • 它共享 Engine 如你所愿
    • 它不使用全局变量(或者,更糟的是,重新分配它们!)
    • 否则的话,它们的行为完全相同,但代码却少得多
        2
  •  3
  •   hassec    8 年前

    我试着举一个例子来说明我认为你想要达到的目标。我希望你不介意我去掉了boost,因为我看不出在这里使用它的理由。

    希望这有助于:

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

    它打印:

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