升压随机数发生器的丢弃行为

时间:2016-08-17 14:10:06

标签: c++ boost-random

我使用适配器类包装boost随机数生成器来实现蒙特卡洛例程。在对类的成员函数编写单元测试时,我假设.discard(unsigned int N)的行为是绘制N个随机数而不存储它们,从而推进了rng的状态。增强代码是:

void discard(boost::uintmax_t z)
{
    if(z > BOOST_RANDOM_MERSENNE_TWISTER_DISCARD_THRESHOLD) {
        discard_many(z);
    } else {
        for(boost::uintmax_t j = 0; j < z; ++j) {
            (*this)();
        }
    }
}

支持我的假设。但是,我发现.discard(1)产生的序列与没有丢弃的相同序列不是一个数字。代码:

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

int main()
{
    boost::mt19937 uGenOne(1);
    boost::variate_generator<boost::mt19937&, boost::normal_distribution<> > distOne(uGenOne, boost::normal_distribution<>());

    boost::mt19937 uGenTwo(1);
    boost::variate_generator<boost::mt19937&, boost::normal_distribution<> > distTwo(uGenTwo, boost::normal_distribution<>());
    distTwo.engine().discard(1);

    unsigned int M = 10;
    std::vector<double> variatesOne(M);
    std::vector<double> variatesTwo(M);

    for (unsigned int m = 0; m < M; ++m) {
        variatesOne[m] = distOne();
        variatesTwo[m] = distTwo();
    }

    for (unsigned int m = 0; m < M; ++m)
        std::cout << std::left << std::setw(15) << variatesOne[m] << variatesTwo[m] << std::endl;

    return 0;
}

输出

2.28493        0.538758  
-0.668627      -0.0017866
0.00680682     0.619191  
0.26211        0.26211   
-0.806832      -0.806832 
0.751338       0.751338  
1.50612        1.50612   
-0.0631903     -0.0631903
0.785654       0.785654  
-0.923125      -0.923125

我对.discard操作错误的解释是什么?为什么前三个输出中的两个序列不同,然后相同?

(此代码在msvc 19.00.23918上编译,g ++ 4.9.2在cygwin上编译,结果相同)。

2 个答案:

答案 0 :(得分:0)

这里的问题似乎是引擎没有被正确修改,或者分布正在增加做一些额外的工作。如果我们直接使用引擎

1.7911e+09     4.28288e+09
4.28288e+09    3.09377e+09
3.09377e+09    4.0053e+09
4.0053e+09     491263
491263         5.5029e+08
5.5029e+08     1.29851e+09
1.29851e+09    4.29085e+09
4.29085e+09    6.30312e+08
6.30312e+08    1.01399e+09
1.01399e+09    3.96591e+08

它产生

boost::variate_generator

由于我们丢弃了第一个输出,因此我们预期的是1个移位序列。

所以你对丢弃的工作方式是正确的。通过{{1}}进行此操作时,我不确定为什么会出现差异。我不明白为什么前三个数字不同但所有其他输出都匹配。

答案 1 :(得分:0)

只是添加上一个答案评论中的重要细节。正如@NathanOliver所提到的,.discard会增加生成器,它会将制服发送到正态分布,这会将制服转换为正态分布。 boost :: normal_distribution使用Ziggurat algorithm这是一种“接受/拒绝”类型的算法。它绘制随机均匀,操纵它,然后检查它是否在所需的分布中。如果没有,它将被拒绝并且是一个新的随机统一。

for(;;) {
        std::pair<RealType, int> vals = generate_int_float_pair<RealType, 8>(eng);
        int i = vals.second;
        int sign = (i & 1) * 2 - 1;
        i = i >> 1;
        RealType x = vals.first * RealType(table_x[i]);
        if(x < table_x[i + 1]) return x * sign;
        if(i == 0) return generate_tail(eng) * sign;
        RealType y = RealType(table_y[i]) + uniform_01<RealType>()(eng) * RealType(table_y[i + 1] - table_y[i]);
        if (y < f(x)) return x * sign;
    }

关键是,如果最后一个if失败,则会再次启动for循环,并再次触发对generate_int_float_pair的调用。这意味着基础生成器递增的次数是未知的。

因此,正常序列将具有不同的数字,直到子序列中的拒绝总和相同的点,此时剩余的均匀序列是相同的。这发生在问题中发布的示例中的第三个位置。 (它实际上有点微妙,因为基础生成器可以在Ziggurat算法中调用一次或两次,但实质是相同的 - 一旦序列同步,它们就永远不会产生不同的变量)。