C++ 梅森捻线机预热与再现性

C++ 梅森捻线机预热与再现性,c++,random,c++11,prng,mersenne-twister,C++,Random,C++11,Prng,Mersenne Twister,在我当前的C++11项目中,我需要执行M模拟。对于每个模拟m=1,…,m,我使用std::mt19937对象随机生成一个数据集,构造如下: std::mt19937 generator(m); DatasetFactory dsf(generator); 根据和,Mersenne Twister PRNG得益于预热阶段,这在我的代码中目前还没有。为了方便起见,我报告了建议的代码片段: #include <random> std::mt19937 get_prng() {

在我当前的C++11项目中,我需要执行M模拟。对于每个模拟
m=1,…,m
,我使用
std::mt19937
对象随机生成一个数据集,构造如下:

std::mt19937 generator(m);
DatasetFactory dsf(generator);
根据和,Mersenne Twister PRNG得益于预热阶段,这在我的代码中目前还没有。为了方便起见,我报告了建议的代码片段:

#include <random>

std::mt19937 get_prng() {
    std::uint_least32_t seed_data[std::mt19937::state_size];
    std::random_device r;
    std::generate_n(seed_data, std::mt19937::state_size, std::ref(r));
    std::seed_seq q(std::begin(seed_data), std::end(seed_data));
    return std::mt19937{q};
}
可能的解决方案#2 下面是一个基于@SteveJassop和@AndréNeve共同贡献的初步实现。
sha256
功能根据

#包括
#包括
#包括
#包括
std::string sha256(const std::string str){
无符号字符哈希[SHA256_摘要_长度];
SHA256_CTX SHA256;
SHA256_Init(&SHA256);
SHA256_更新(&SHA256,str.c_str(),str.size());
SHA256_Final(散列和SHA256);
std::stringstream-ss;
对于(int i=0;iss我认为您只需要存储初始种子(在您的情况下,std::uint_least32_t seed_数据[std::mt19937::state_size]
数组)和您希望重现的每次运行/模拟的
n
预热步骤的数量(例如,使用
discard(n)

有了这些信息,您始终可以创建一个新的MT实例,使用以前的
seed_数据对其进行种子设定,并以相同的
n
预热步骤运行它。这将生成相同的值序列,因为预热结束时MT实例将具有相同的内部状态

当您提到影响再现性的
std::random_设备时,我相信在您的代码中,它只是用来生成种子数据。如果您将其本身用作随机数的来源,那么您将无法获得可再现的结果。因为您只使用它来生成种子,所以不应该存在错误纽约问题。如果你想复制值,你不能每次都生成一个新种子

根据
std::random_设备的定义

“std::random_设备是一个均匀分布的整数随机数生成器,可生成非确定性随机数。”

因此,如果它不是确定性的,你就不能复制它产生的值序列。也就是说,只需使用它来生成好的随机种子,然后再存储它们,以便重新运行

希望这有帮助

编辑:

在与@SteveJessop讨论之后,我们得出结论,数据集(或其中的一部分)的简单散列将足以用作您需要的合适种子。这允许在每次运行模拟时以确定性方式生成相同的种子。正如@Steve所述,您必须保证哈希的大小与
std::mt19937::state_size
相比不会太小。如果太小,则y正如他所建议的,你可以连接m,m+m,m+2M的散列,直到你有足够的数据

我在这里发布更新后的答案,因为使用散列的想法是我的,但我将投票支持@SteveJessop的答案,因为他对此做出了贡献。

两个选项:

  • 按照你的建议,但不要使用
    std::random_device r;
    为MT生成种子序列,而是使用不同的PRNG,使用
    m
    种子。选择一个不会像MT那样在使用小种子数据时需要预热的PRNG:我怀疑LCG可能会这样做。对于大规模的过度杀伤,你可以n使用基于安全散列的PRNG。如果你听说过的话,这很像密码学中的“密钥拉伸”。事实上,你可以使用标准的密钥拉伸算法,但你正在使用它生成一个长的种子序列,而不是大的密钥材料

  • 继续使用
    m
    为您的机器翻译设定种子,但
    在开始模拟之前丢弃大量恒定的数据。也就是说,忽略使用强种子的建议,而是运行机器翻译足够长的时间,以使其达到一个良好的内部状态。我不知道您需要丢弃多少数据,但我希望互联网是这样的


  • 您链接到的其中一个答案上的注释表示:

    巧合的是,默认的C++11种子序列是Mersenne Twister预热序列(尽管现有的实现,例如libc++的mt19937,在提供单值种子时使用更简单的预热)

    因此,您可以将当前的固定种子与
    std::seed_seq
    一起用于热身

    std::mt19937 get_prng(int seed) {
        std::seed_seq q{seed, maybe, some, extra, fixed, values};
        return std::mt19937{q};
    }
    

    你不能从PRNG中读取固定数量的数据吗?你的意思是,当你要求新数据时,伪随机序列的质量会提高吗?我的目标是在初始化阶段明确考虑
    std::mt19937::state_size
    ,同时保留可再现性。所有随机数生成器都有一个成员函数离子
    discard(n)
    将内部状态提升,就像调用
    operator()
    n
    discard(n)
    操作是否达到了使用与
    std::seed
    一样大的
    std::mt19937::state_size
    为PRNG种子的相同结果?要使用的
    n
    “可能的2",
    std::hash
    不够好。您试图克服的MT问题是,它需要大量非零位种子数据,否则其内部状态大多为0,输出的数据不好。
    std::hash
    不是解决这一问题的正确哈希类型。它最多只能提供64位种子数据,更糟糕的是因为它很可能是身份操作。例如,如果您使用
    m
    的SHA256散列,那么您可能正在做生意。我需要创建
    m
    不同的数据集,这些数据集将在
    m
    不同的模拟中使用。我需要数据集是独立的,即每个数据集都是生成的
    #include <openssl/sha.h>
    #include <sstream>
    #include <iomanip>
    #include <random>
    
     std::string sha256(const std::string str) {
        unsigned char hash[SHA256_DIGEST_LENGTH];
        SHA256_CTX sha256;
        SHA256_Init(&sha256);
        SHA256_Update(&sha256, str.c_str(), str.size());
        SHA256_Final(hash, &sha256);
    
        std::stringstream ss;
        for(int i = 0; i < SHA256_DIGEST_LENGTH; i++) 
            ss << std::hex << std::setw(2) << std::setfill('0') << (int)hash[i];
    
        return ss.str();
    }
    
    std::mt19937 get_generator(unsigned int seed) {
        std::string seed_str = sha256(std::to_string(seed));
        std::seed_seq q(seed_str.begin(), seed_str.end());
        return std::mt19937{q};
    }
    
    std::mt19937 get_prng(int seed) {
        std::seed_seq q{seed, maybe, some, extra, fixed, values};
        return std::mt19937{q};
    }