Est-ce que std :: mt19937 nécessite un échauffement?

J’ai lu que de nombreux générateurs de nombres pseudo-aléatoires nécessitent de nombreux échantillons pour être “réchauffés”. Est-ce le cas lorsque vous utilisez std :: random_device pour générer std :: mt19937, ou pouvons-nous nous attendre à ce qu’il soit prêt après la construction? Le code en question:

#include  std::random_device rd; std::mt19937 gen(rd()); 

Mersenne Twister est un pRNG (générateur de nombres pseudo-aléatoires) basé sur un registre à décalage et est donc sujet à de mauvaises graines avec de longues séries de 0 ou de 1 qui conduisent à des résultats relativement prévisibles jusqu’à ce que l’état interne soit suffisamment mélangé.

Cependant, le constructeur qui prend une seule valeur utilise une fonction compliquée sur cette valeur de départ, conçue pour minimiser la probabilité de produire de tels «mauvais» états. Il y a une seconde façon d’initialiser mt19937 où vous définissez directement l’état interne, via un object conforme au concept SeedSequence. C’est cette deuxième méthode d’initialisation où vous devrez peut-être vous préoccuper du choix d’un «bon» état ou de la mise en température.


La norme inclut un object conforme au concept SeedSequence, appelé seed_seq . seed_seq prend un nombre arbitraire de valeurs de départ, puis effectue certaines opérations sur ces valeurs afin de produire une séquence de valeurs différentes permettant de définir directement l’état interne d’un pRNG.

Voici un exemple de chargement d’une séquence de départ avec suffisamment de données aléatoires pour remplir tout l’état std::mt19937 :

 std::array seed_data; std::random_device r; std::generate_n(seed_data.data(), seed_data.size(), std::ref(r)); std::seed_seq seq(std::begin(seed_data), std::end(seed_data)); std::mt19937 eng(seq); 

Cela garantit que l’état entier est randomisé. En outre, chaque moteur spécifie la quantité de données qu’il lit à partir de la graine_sequence. Il est donc possible que vous souhaitiez lire les documents pour trouver les informations correspondant au moteur que vous utilisez.

Bien que ici je charge le seed_seq entièrement à partir de std::random_device , seed_seq est spécifié de telle sorte que quelques nombres qui ne sont pas particulièrement aléatoires devraient fonctionner correctement. Par exemple:

 std::seed_seq seq{1, 2, 3, 4, 5}; std::mt19937 eng(seq); 

Dans les commentaires ci-dessous, Cubbi indique que seed_seq fonctionne en effectuant une séquence de préchauffage pour vous.

Voici ce que devrait être votre “défaut” pour l’ensemencement:

 std::random_device r; std::seed_seq seed{r(), r(), r(), r(), r(), r(), r(), r()}; std::mt19937 rng(seed); 

Je crois qu’il y a des situations où la MT peut être «mal traitée», ce qui entraîne des séquences non optimales. Si je me souviens bien, semer avec tous les zéros est un de ces cas. Je vous recommande d’essayer d’utiliser les générateurs WELL si c’est un problème grave pour vous. Je pense qu’ils sont plus flexibles – la qualité des semences importe moins. (Peut-être pour répondre plus directement à votre question: il est probablement plus efficace de se concentrer sur l’ensemencement plutôt que de semer mal puis d’essayer de générer un tas d’échantillons pour amener le générateur à un état optimal.)

Si vous semez avec une seule valeur de 32 bits, tout ce que vous obtiendrez sera l’une des mêmes 2 ^ 32 trajectoires à travers l’espace d’état. Si vous utilisez un PRNG avec des KiB d’état, vous devriez probablement tout semer. Comme décrit dans les commentaires de @bames63 ‘, utiliser std::seed_seq n’est probablement pas une bonne idée si vous voulez initier l’état entier avec des nombres aléatoires. Malheureusement, std::random_device n’est pas conforme au concept SeedSequence , mais vous pouvez écrire un wrapper qui fait:

 #include  #include  #include  #include  class random_device_wrapper { std::random_device *m_dev; public: using result_type = std::random_device::result_type; explicit random_device_wrapper(std::random_device &dev) : m_dev(&dev) {} template  void generate(RandomAccessIterator first, RandomAccessIterator last) { std::generate(first, last, std::ref(*m_dev)); } }; int main() { auto rd = std::random_device{}; auto seedseq = random_device_wrapper{rd}; auto mt = std::mt19937{seedseq}; for (auto i = 100; i; --i) std::cout << mt() << std::endl; } 

Cela fonctionne au moins jusqu'à ce que vous activiez des concepts. Selon que votre compilateur connaît SeedSequence tant que concept C ++ 20, il risque de ne pas fonctionner car nous ne fournissons que la méthode generate() manquante, rien d'autre. Dans la programmation de gabarits de type canard, ce code est suffisant car le PRNG ne stocke pas l’object de la séquence de départ.