私はモンテカルロシミュレーションを実行するプログラムに取り組んでいます。具体的には、メトロポリスアルゴリズムを使用しています。プログラムは、おそらく数十億の「乱数」を生成する必要があります。メルセンヌツイスターはモンテカルロシミュレーションで非常に人気があることは知っていますが、可能な限り最善の方法でジェネレーターをシードしていることを確認したいと思います。
現在、次の方法を使用して32ビットシードを計算しています。
mt19937_64 prng; //pseudo random number generator
unsigned long seed; //store seed so that every run can follow the same sequence
unsigned char seed_count; //to help keep seeds from repeating because of temporal proximity
unsigned long genSeed() {
return ( static_cast<unsigned long>(time(NULL)) << 16 )
| ( (static_cast<unsigned long>(clock()) & 0xFF) << 8 )
| ( (static_cast<unsigned long>(seed_count++) & 0xFF) );
}
//...
seed = genSeed();
prng.seed(seed);
繰り返されない新しいシードを保証するためのより良い方法があると感じています。mt19937_64には32ビット以上をシードできると確信しています。誰か提案がありますか?
要約してみましょう(コメントも)。次の各オカレンスで乱数の独立したシーケンスを取得するために、さまざまなシードを生成したいと思います。
1はエポック以降の時間を使用して解決され、2はグローバルアトミックカウンターで解決され、3はプラットフォームに依存するIDで解決されます( クロスプラットフォームの方法で(ほぼ)一意のシステム識別子を取得する方法? を参照)
ここで重要なのは、それらを組み合わせてuint_fast64_t
(std::mt19937_64
のシードタイプ)を取得するための最良の方法は何ですか?ここでは、各パラメーターの範囲が事前にわからないか、パラメーターが大きすぎるため、ビットシフトで簡単な方法で一意のシードを取得することはできないと想定しています。
std::seed_seq
が簡単な方法ですが、戻り値の型uint_least32_t
は最善の選択ではありません。
優れた64ビットハッシャーははるかに優れた選択肢です。 STLはfunctional
ヘッダーの下にstd::hash
を提供します。可能性としては、上記の3つの数値を文字列に連結し、それをハッシャーに渡します。戻り値の型はsize_t
であり、64台のマシンでは要件に一致する可能性が非常に高くなります。
衝突は起こりそうにありませんが、もちろん可能です。シーケンスを複数回含む統計を作成したくない場合は、シードを保存して重複した実行を破棄することしかできません。
std::random_device
を使用してシードを生成することもできます(衝突はまだ発生する可能性があり、多かれ少なかれ頻繁かどうかはわかりません)が、実装はライブラリに依存し、疑似ランダムジェネレーターになる可能性があるため必須ですデバイスのエントロピーをチェックし、この目的でゼロエントロピーデバイスを使用しないようにします。おそらく上記のポイント(特にポイント3)を壊してしまうからです。残念ながら、エントロピーを発見できるのは、プログラムを特定のマシンに持っていき、インストールされているライブラリでテストする場合だけです。
std::random_device
を使用してシードを生成します。実装でサポートされている場合は、非決定論的な乱数が提供されます。それ以外の場合は、他の乱数エンジンを使用できます。
std::mt19937_64 prng;
seed = std::random_device{}();
prng.seed(seed);
std::random_device
のoperator()
はunsigned int
を返すため、プラットフォームに32ビットのint
sがあり、64ビットのシードが必要な場合は、を呼び出す必要があります。それを2回。
std::mt19937_64 prng;
std::random_device device;
seed = (static_cast<uint64_t>(device()) << 32) | device();
prng.seed(seed);
もう1つの利用可能なオプションは、 std::seed_seq
を使用してPRNGをシードすることです。これにより、PRNGはseed_seq::generate
を呼び出すことができ、[0≤i<2の範囲でバイアスのないシーケンスが生成されます。32)、状態全体を満たすのに十分な大きさの出力範囲。
std::mt19937_64 prng;
std::random_device device;
std::seed_seq seq{device(), device(), device(), device()};
prng.seed(seq);
random_device
を4回呼び出して、seed_seq
の4要素の初期シーケンスを作成します。ただし、最初のシーケンスの要素の長さまたはソースに関する限り、これのベストプラクティスが何であるかはわかりません。
あなたのコメントからわかる限り、あなたが興味を持っているのは、プロセスがいくつかのシミュレーションをまったく同時に開始した場合に、それらが異なるシードを取得することを保証することだと思われます。
現在のアプローチで私が見ることができる唯一の重要な問題は競合状態です。複数のシミュレーションを同時に開始する場合は、別々のスレッドから実行する必要があります。別々のスレッドから実行する場合は、スレッドセーフな方法でseed_count
を更新する必要があります。そうしないと、複数のシミュレーションが同じseed_count
になる可能性があります。それを解決するには、単にstd::atomic<int>
にすることができます。
それを超えて、それは必要以上に複雑に思えます。 2つの別々のタイマーを使用することで何が得られますか?あなたはこれと同じくらい簡単なことをすることができます:
seed_count
のように、シミュレーションが開始するたびにインクリメントされます。どうですか...
スレッドを開始するいくつかのメインコードがあり、それらのスレッドで実行される関数のコピーがあり、各コピーには独自のMarsenneTwisterがあります。私は正しいですか?もしそうなら、メインコードで別のランダムジェネレーターを使用してみませんか?タイムスタンプがシードされ、連続する疑似乱数がシードとして関数インスタンスに送信されます。
POSIX関数 gettimeofday(2)
は、マイクロ秒の精度で時間を示します。
POSIXスレッド関数 gettid(2)
は、現在のスレッドのID番号を返します。
エポック(すでに使用している)からの秒単位の時間、マイクロ秒単位の時間、およびスレッドIDを組み合わせて、1台のマシンで常に一意のシードを取得できるはずです。
複数のマシン間で一意である必要がある場合は、ホスト名、IPアドレス、またはMACアドレスも取得することを検討できます。
40億を超える固有のシードが利用可能であるため、おそらく32ビットで十分だと思います。数十億のプロセスを実行している可能性が低い場合を除いて、64ビットシードを使用しなくても大丈夫です。
コメントから、アルゴリズムの複数のインスタンスを実行したいことがわかりました。スレッドごとに1つのインスタンスです。また、各インスタンスのシードがほぼ同時に生成されることを考えると、これらのシードが異なることを確認する必要があります。それが実際に解決しようとしているものである場合、genSeed関数は必ずしもそれを保証するわけではありません。
私の意見では、必要なのは並列化可能な乱数ジェネレーター(RNG)です。これが意味するのは、oneRNGだけが必要であり、これはoneシード(genSeedで生成できます)次に、通常は生成される乱数のシーケンスシーケンシャル環境は、重複しないX個のシーケンスに分割されます。ここで、Xはスレッドの数です。これらのタイプのRNGをC++で提供し、RNGのC++標準に準拠し、TRNG( http://numbercrunch.de/trng )と呼ばれる非常に優れたライブラリがあります。
ここにもう少し情報があります。スレッドごとに重複しないシーケンスを実現する方法は2つあります。 singleRNGからの乱数のシーケンスがr = {r(1)であると仮定しましょう。 、r(2)、r(3)、...}で、スレッドは2つだけです。スレッドごとに必要な乱数の数、たとえばMが事前にわかっている場合は、rシーケンスの最初のMを最初のスレッドに与えることができます。つまり{r(1)、r(2)、...、r (M)}、および2番目のMから2番目のスレッド、つまり{r(M + 1)、r(M + 2)、... r(2M)}。この手法は、シーケンスを2つの連続するブロックに分割するため、ブロック分割と呼ばれます。
2番目の方法は、最初のスレッドのシーケンスを{r(1)、r(3)、r(5)、...}として作成し、2番目のスレッドのシーケンスを{r(2)、r(4)、 r(6)、...}。これには、スレッドごとに必要な乱数の数を事前に知る必要がないという利点があります。これは跳躍と呼ばれます。
両方のメソッドは、スレッドごとのシーケンスが実際に重複していないことを保証することに注意してください。上に投稿したリンクには多くの例があり、ライブラリ自体は非常に使いやすいです。私の投稿がお役に立てば幸いです。