Я работаю над программой, которая запускает симуляцию Монте-Карло; в частности, я использую алгоритм Метрополис. Программа должна генерировать, возможно, миллиарды «случайных» чисел. Я знаю, что твистер Мерсенна очень популярен для симуляции Монте-Карло, но я хотел бы убедиться, что я сею генератор наилучшим образом.
В настоящее время я вычисляю 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 решается с помощью зависимого от платформы идентификатора (см. Как получить (почти) уникальный системный идентификатор кроссплатформенным способом?)
Теперь дело в том, что является лучшим способом объединить их, чтобы получить uint_fast64_t
(тип семян std::mt19937_64
)? Здесь я предполагаю, что мы априори не знаем диапазон каждого параметра или что они слишком велики, поэтому мы не можем просто играть с битовыми сдвигами, получая уникальное начальное число тривиальным способом.
std::seed_seq
будет легкий путь, однако его тип возврата uint_least32_t
это не наш лучший выбор.
Хороший 64-битный хеш — гораздо лучший выбор. STL предлагает std::hash
под functional
заголовок, возможно объединить три числа выше в строку и затем передать ее хешу. Тип возвращаемого значения size_t
который на 64 машинах очень вероятно, чтобы соответствовать нашим требованиям.
Столкновения маловероятны, но, конечно, возможны, если вы не хотите собирать статистику, включающую последовательность более одного раза, вы можете только сохранить начальные числа и отбросить дублированные прогоны.
std::random_device
может также использоваться для генерации начальных чисел (коллизии могут все еще происходить, трудно сказать, если более или менее часто), однако, поскольку реализация зависит от библиотеки и может переходить к генератору псевдослучайных данных, необходимо проверить энтропию Не используйте устройство с нулевой энтропией для этой цели, поскольку вы, вероятно, нарушите вышеприведенные пункты (особенно пункт 3). К сожалению, вы можете обнаружить энтропию только тогда, когда вы переносите программу на конкретный компьютер и проводите тестирование с установленной библиотекой.
использование std::random_device
произвести семя. Это обеспечит недетерминированные случайные числа, если ваша реализация поддерживает это. В противном случае разрешается использовать другой движок случайных чисел.
std::mt19937_64 prng;
seed = std::random_device{}();
prng.seed(seed);
operator()
из std::random_device
возвращает unsigned int
, так что если ваша платформа имеет 32-битную int
s, и вы хотите 64-битное начальное число, вам нужно будет вызвать его дважды.
std::mt19937_64 prng;
std::random_device device;
seed = (static_cast<uint64_t>(device()) << 32) | device();
prng.seed(seed);
Другой доступный вариант использует std::seed_seq
посеять PRNG. Это позволяет PRNG звонить seed_seq::generate
, которая производит несмещенную последовательность в диапазоне [0 ≤ я < 232), с выходным диапазоном, достаточно большим, чтобы заполнить все его состояние.
std::mt19937_64 prng;
std::random_device device;
std::seed_seq seq{device(), device(), device(), device()};
prng.seed(seq);
Я звоню random_device
4 раза, чтобы создать начальную последовательность из 4 элементов для seed_seq
, Тем не менее, я не уверен, что наилучшая практика для этого, что касается длины или источника элементов в начальной последовательности.
Насколько я могу судить по вашим комментариям, кажется, что вас интересует, чтобы убедиться, что если процесс запустит несколько ваших симуляций в одно и то же время, они получат разные семена.
Единственная существенная проблема, которую я вижу в вашем текущем подходе, — это состояние гонки: если вы собираетесь запустить несколько симуляций одновременно, это должно быть сделано из отдельных потоков. Если это сделано из отдельных потоков, вам необходимо обновить seed_count
потокобезопасным способом, или несколько симуляций могут закончиться одним и тем же seed_count
, Вы могли бы просто сделать это std::atomic<int>
чтобы решить это.
Кроме того, это кажется более сложным, чем должно быть. Что вы получаете, используя два отдельных таймера? Вы можете сделать что-то простое, как это:
seed_count
,Как насчет…
Существует некоторый основной код, который запускает потоки, и есть копии функций, запускаемых в этих потоках, каждая из которых имеет свой собственный марсеннский Twister. Я прав? Если это так, почему бы не использовать другой генератор случайных чисел в основном коде? Он будет заполнен меткой времени и отправит его последовательные псевдослучайные числа в экземпляры функций в качестве своих начальных значений.
Функция POSIX gettimeofday(2)
дает время с точностью до микросекунды.
Функция потока POSIX gettid(2)
возвращает идентификационный номер текущего потока.
Вы должны быть в состоянии объединить время в секундах с начала эпохи (которое вы уже используете), время в микросекундах и идентификатор потока, чтобы получить начальное число, которое всегда уникально на одной машине.
Если вам также нужно, чтобы он был уникальным на нескольких компьютерах, вы также можете рассмотреть возможность получения имени хоста, IP-адреса или MAC-адреса.
Я предполагаю, что 32 бита, вероятно, достаточно, так как доступно более 4 миллиардов уникальных семян. Если вы не запускаете миллиарды процессов, что маловероятно, вы должны быть в порядке, не переходя на 64-битные начальные числа.
Из комментариев я понимаю, что вы хотите запустить несколько экземпляров алгоритма, по одному экземпляру на поток. И учитывая, что начальное число для каждого экземпляра будет сгенерировано в значительной степени одновременно, вы хотите убедиться, что эти начальные значения различны. Если это действительно то, что вы пытаетесь решить, то ваша функция genSeed не обязательно гарантирует это.
На мой взгляд, вам нужен параллелизуемый генератор случайных чисел (ГСЧ). Это означает, что вам нужно только один ГСЧ, с которым вы работаете только один seed (который вы можете сгенерировать с помощью genSeed), а затем последовательность случайных чисел, которые обычно бывают образованы в последовательной среде, разделяется на X непересекающихся последовательностей; где X — количество потоков. Существует очень хорошая библиотека, которая предоставляет RNG такого типа в C ++, соответствует стандарту C ++ для RNG и называется TRNG (http://numbercrunch.de/trng).
Вот немного больше информации. Есть два способа достижения неперекрывающихся последовательностей на поток. Предположим, что последовательность случайных чисел из не замужем RNG — это r = {r (1), r (2), r (3), …}, и у вас есть только два потока. Если вы заранее знаете, сколько случайных чисел вам понадобится для каждого потока, скажем, M, вы можете передать первый M последовательности r первому потоку, то есть {r (1), r (2), …, r (M)} и второй M во второй поток, т. Е. {R (M + 1), r (M + 2), … r (2M)}. Этот метод называется разбиением блоков, так как вы разделяете последовательность на два последовательных блока.
Второй способ — создать последовательность для первого потока как {r (1), r (3), r (5), …}, а для второго потока — как {r (2), r (4), r (6), …}, преимущество в том, что вам не нужно заранее знать, сколько случайных чисел вам понадобится на поток. Это называется скачком.
Обратите внимание, что оба метода гарантия что последовательности на поток действительно не перекрываются. Ссылка, которую я разместил выше, содержит много примеров, а сама библиотека чрезвычайно проста в использовании. Надеюсь мой пост поможет.