Генерация случайных чисел
Генерация случайных чисел в Julia по умолчанию использует алгоритм Xoshiro256++, с по-Task состоянием. Другие типы генераторов случайных чисел могут быть подключены путём наследования типа AbstractRNG; затем они могут использоваться для получения нескольких потоков случайных чисел.
Генераторы псевдослучайных чисел (PRNG), экспортируемые пакетом Random, это:
-
TaskLocalRNG: маркер, представляющий использование текущего активного потока задачи, детерминированно засеянного из родительской задачи, илиRandomDevice(с использованием системной случайности) при запуске программы -
Xoshiro: генерирует поток высококачественных случайных чисел с небольшим вектором состояния и высокой производительностью, используя алгоритм Xoshiro256++ -
RandomDevice: для получения энтропии от операционной системы. Может использоваться для криптографически безопасных случайных чисел (CS(P)RNG). -
MersenneTwister: альтернативный высококачественный генератор PRNG, который был по умолчанию в старых версиях Julia и также довольно быстрый, но требует гораздо больше места для хранения вектора состояния и генерации случайной последовательности.
Большинство функций, связанных с генерацией случайных чисел, принимают необязательный объект AbstractRNG в качестве первого аргумента. Некоторые также принимают спецификации размерности dims... (которые также могут быть заданы как кортеж) для генерации массивов случайных значений. В многопоточной программе, как правило, следует использовать разные объекты RNG для разных потоков или задач, чтобы обеспечить безопасность потоков. Однако, генератор RNG по умолчанию безопасен для потоков начиная с Julia 1.3 (используя генератор RNG на поток до версии 1.6, а затем - на задачу).
Предоставленные генераторы могут генерировать равномерно распределённые случайные числа следующих типов: Float16, Float32, Float64, BigFloat, Bool, Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Int128, UInt128, BigInt (или комплексные числа этих типов). Случайные числа с плавающей точкой генерируются равномерно в $[0, 1)$. Поскольку BigInt представляет неограниченные целые числа, интервал должен быть задан (например, rand(big.(1:6))).
Кроме того, нормальное и экспоненциальное распределения реализованы для некоторых типов AbstractFloat и Complex, см. randn и randexp для подробностей.
Для генерации случайных чисел из других распределений, см. пакет Distributions.jl.
Поскольку точный способ генерации случайных чисел считается деталью реализации, исправления ошибок и улучшения производительности могут изменить поток чисел, генерируемых после изменения версии. Поэтому не рекомендуется полагаться на конкренное семя или сгенерированный поток чисел при тестировании модулей — вместо этого следует тестировать свойства самих методов.
Модуль случайных чисел
Random.RandomМодуль
Random
Поддержка генерации случайных чисел. Предоставляет rand, randn, AbstractRNG, MersenneTwister и RandomDevice.
Функции генерации случайных чисел
Base.randФункция
rand([rng=default_rng()], [S], [dims...])
Выбирает случайный элемент или массив случайных элементов из набора значений, указанного в S; S может быть
- индексируемым набором (например,
1:9или('x', "y", :z)), - объектом
AbstractDictилиAbstractSet, - строкой (рассматривается как набор символов), или
- типом: набор значений для выбора тогда эквивалентен
typemin(S):typemax(S)для целых чисел (это не относится кBigInt), к $[0, 1)$ для чисел с плавающей точкой и к $[0, 1)+i[0, 1)$ для комплексных чисел с плавающей точкой;
S по умолчанию равен Float64. Когда передаётся только один аргумент помимо необязательного rng и он является Tuple, он интерпретируется как набор значений (S ) и не как dims.
См. также randn для нормально распределённых чисел и rand! и randn! для эквивалентов на месте.
Поддержка S как кортежа требует по крайней мере Julia 1.1.
Примеры
julia> rand(Int, 2)
2-element Array{Int64,1}:
1339893410598768192
1575814717733606317
julia> using Random
julia> rand(MersenneTwister(0), Dict(1=>2, 3=>4))
1=>2
julia> rand((2, 3))
3
julia> rand(Float64, (2, 3))
2×3 Array{Float64,2}:
0.999717 0.0143835 0.540787
0.696556 0.783855 0.938235
Сложность rand(rng, s::Union{AbstractDict,AbstractSet}) линейна по длине s, за исключением случаев, когда доступен оптимизированный метод с постоянной сложностью, что имеет место для Dict, Set и плотных BitSet. Для более чем нескольких вызовов используйте rand(rng, collect(s)) вместо этого, или rand(rng, Dict(s)) или rand(rng, Set(s)) соответственно.
Random.rand!Функция
rand!([rng=default_rng()], A, [S=eltype(A)])
Заполняет массив A случайными значениями. Если указано S (S может быть типом или набором, см. rand для подробностей), значения выбираются случайным образом из S. Это эквивалентно copyto!(A, rand(rng, S, size(A))), но без выделения нового массива.
Примеры
julia> rng = MersenneTwister(1234);
julia> rand!(rng, zeros(5))
5-element Vector{Float64}:
0.5908446386657102
0.7667970365022592
0.5662374165061859
0.4600853424625171
0.7940257103317943
Random.bitrandФункция
bitrand([rng=default_rng()], [dims...])
Генерирует BitArray случайных значений типа булево.
Примеры
julia> rng = MersenneTwister(1234); julia> bitrand(rng, 10) 10-element BitVector: 0 0 0 0 1 0 0 0 1 1
Base.randnФункция
randn([rng=default_rng()], [T=Float64], [dims...])
Генерирует нормально распределённое случайное число типа T со средним значением 0 и стандартным отклонением 1. Необязательно генерировать массив нормально распределённых случайных чисел. Модуль Base в настоящее время предоставляет реализацию для типов Float16, Float32 и Float64 (по умолчанию) и их комплексных аналогов. Когда аргумент типа комплексный, значения выбираются из кругосимметричного комплексного нормального распределения с дисперсией 1 (что соответствует тому, что вещественная и мнимая части имеют независимое нормальное распределение со средним значением ноль и дисперсией 1/2).
См. также randn! для работы на месте.
Примеры
julia> using Random
julia> rng = MersenneTwister(1234);
julia> randn(rng, ComplexF64)
0.6133070881429037 - 0.6376291670853887im
julia> randn(rng, ComplexF32, (2, 3))
2×3 Matrix{ComplexF32}:
-0.349649-0.638457im 0.376756-0.192146im -0.396334-0.0136413im
0.611224+1.56403im 0.355204-0.365563im 0.0905552+1.31012im
Random.randn!Функция
randn!([rng=default_rng()], A::AbstractArray) -> A
Заполняет массив A нормально распределёнными (среднее 0, стандартное отклонение 1) случайными числами. Также см. функцию rand.
Примеры
julia> rng = MersenneTwister(1234);
julia> randn!(rng, zeros(5))
5-element Vector{Float64}:
0.8673472019512456
-0.9017438158568171
-0.4944787535042339
-0.9029142938652416
0.8644013132535154
Random.randexpФункция
randexp([rng=default_rng()], [T=Float64], [dims...])
Генерирует случайное число типа T согласно экспоненциальному распределению со масштабом 1. Необязательно генерирует массив таких случайных чисел. Модуль Base в настоящее время предоставляет реализацию для типов Float16, Float32 и Float64 (по умолчанию).
Примеры
julia> rng = MersenneTwister(1234);
julia> randexp(rng, Float32)
2.4835055f0
julia> randexp(rng, 3, 3)
3×3 Matrix{Float64}:
1.5167 1.30652 0.344435
0.604436 2.78029 0.418516
0.695867 0.693292 0.643644
Random.randexp!Функция
randexp!([rng=default_rng()], A::AbstractArray) -> A
Заполняет массив A случайными числами, следующими экспоненциальному распределению (со масштабом 1).
Примеры
julia> rng = MersenneTwister(1234);
julia> randexp!(rng, zeros(5))
5-element Vector{Float64}:
2.4835053723904896
1.516703605376473
0.6044364871025417
0.6958665886385867
1.3065196315496677
Random.randstringФункция
randstring([rng=default_rng()], [chars], [len=8])
Создаёт случайную строку длиной len, состоящую из символов из chars, по умолчанию — это набор прописных и строчных букв и цифр от 0 до 9. Необязательный аргумент rng задаёт генератор случайных чисел, см. Случайные числа.
Примеры
julia> Random.seed!(3); randstring()
"Lxz5hUwn"
julia> randstring(MersenneTwister(3), 'a':'z', 6)
"ocucay"
julia> randstring("ACGT")
"TGCTCCTC"
chars может быть любым набором символов типа Char или UInt8 (более эффективно), при условии, что rand может случайно выбирать символы из него.
Подпоследовательности, перестановки и перемешивание
Random.randsubseqФункция
randsubseq([rng=default_rng(),] A, p) -> Vector
Возвращает вектор, состоящий из случайной подпоследовательности заданного массива A, где каждый элемент A включается (в порядке) с независимой вероятностью p. (Сложность линейна по отношению к p*length(A), поэтому эта функция эффективна даже если p мал, а A велик). В техническом плане этот процесс известен как «выборка Бернулли» A.
Примеры
julia> rng = MersenneTwister(1234);
julia> randsubseq(rng, 1:8, 0.3)
2-element Vector{Int64}:
7
8
Random.randsubseq!Функция
randsubseq!([rng=default_rng(),] S, A, p)
Подобно randsubseq, но результаты хранятся в S (размер которого увеличивается по мере необходимости).
Примеры
julia> rng = MersenneTwister(1234);
julia> S = Int64[];
julia> randsubseq!(rng, S, 1:8, 0.3)
2-element Vector{Int64}:
7
8
julia> S
2-element Vector{Int64}:
7
8
Random.randpermФункция
randperm([rng=default_rng(),] n::Integer)
Строит случайную перестановку длины n. Необязательный аргумент rng задаёт генератор случайных чисел (см. Случайные числа). Тип элементов результата совпадает с типом n.
Для случайной перестановки произвольного вектора см. shuffle или shuffle!.
В Julia 1.1 randperm возвращает вектор v с eltype(v) == typeof(n), в то время как в Julia 1.0 eltype(v) == Int.
Примеры
julia> randperm(MersenneTwister(1234), 4)
4-element Vector{Int64}:
2
1
4
3
Random.randperm!Функция
randperm!([rng=default_rng(),] A::Array{<:Integer})
Строит в A случайную перестановку длины length(A). Необязательный аргумент rng задаёт генератор случайных чисел (см. Случайные числа). Для случайной перестановки произвольного вектора см. shuffle или shuffle!.
Примеры
julia> randperm!(MersenneTwister(1234), Vector{Int}(undef, 4))
4-element Vector{Int64}:
2
1
4
3
Random.randcycleФункция
randcycle([rng=default_rng(),] n::Integer)
Строит случайную циклическую перестановку длины n. Необязательный аргумент rng задаёт генератор случайных чисел, см. Случайные числа. Тип элементов результата совпадает с типом n.
В Julia 1.1 randcycle возвращает вектор v с eltype(v) == typeof(n), в то время как в Julia 1.0 eltype(v) == Int.
Примеры
julia> randcycle(MersenneTwister(1234), 6)
6-element Vector{Int64}:
3
5
4
6
1
2
Random.randcycle!Функция
randcycle!([rng=default_rng(),] A::Array{<:Integer})
Строит в A случайную циклическую перестановку длины length(A). Необязательный аргумент rng задаёт генератор случайных чисел, см. Случайные числа.
Примеры
julia> randcycle!(MersenneTwister(1234), Vector{Int}(undef, 6))
6-element Vector{Int64}:
3
5
4
6
1
2
Random.shuffleФункция
shuffle([rng=default_rng(),] v::AbstractArray)
Возвращает случайную перестановку копии v. Необязательный аргумент rng задаёт генератор случайных чисел (см. Случайные числа). Для перестановки v на месте см. shuffle!. Для получения случайных переставленных индексов см. randperm.
Примеры
julia> rng = MersenneTwister(1234);
julia> shuffle(rng, Vector(1:10))
10-element Vector{Int64}:
6
1
10
2
3
9
5
7
4
8
Random.shuffle!Функция
shuffle!([rng=default_rng(),] v::AbstractArray)
Функция shuffle в режиме изменения: случайная перестановка v на месте, с необязательным указанием генератора случайных чисел rng.
Примеры
julia> rng = MersenneTwister(1234);
julia> shuffle!(rng, Vector(1:16))
16-element Vector{Int64}:
2
15
5
14
1
9
10
6
11
3
16
7
4
12
8
13Генераторы (создание и инициализация)
Random.default_rngФункция
default_rng() -> rng
Возвращает глобальный генератор случайных чисел (RNG) по умолчанию.
Генератор по умолчанию — это реализационная деталь. В разных версиях Julia вы не должны ожидать, что генератор по умолчанию будет одинаковым, или что он будет возвращать одинаковую последовательность случайных чисел для заданного зерна.
Эта функция была введена в Julia 1.3.
Random.seed!Функция
seed!([rng=default_rng()], seed) -> rng seed!([rng=default_rng()]) -> rng
Перезадаёт зерно генератора случайных чисел: rng обеспечит воспроизводимую последовательность чисел только при условии предоставления seed. Некоторые генераторы RNG не принимают зерно, например, RandomDevice. После вызова seed!, rng эквивалентен вновь созданному объекту, инициализированному тем же зерном.
Если rng не указано, используется зерно состояния общего генератора локальной задачи.
Примеры
julia> Random.seed!(1234);
julia> x1 = rand(2)
2-element Vector{Float64}:
0.32597672886359486
0.5490511363155669
julia> Random.seed!(1234);
julia> x2 = rand(2)
2-element Vector{Float64}:
0.32597672886359486
0.5490511363155669
julia> x1 == x2
true
julia> rng = Xoshiro(1234); rand(rng, 2) == x1
true
julia> Xoshiro(1) == Random.seed!(rng, 1)
true
julia> rand(Random.seed!(rng), Bool) # not reproducible
true
julia> rand(Random.seed!(rng), Bool) # not reproducible either
false
julia> rand(Xoshiro(), Bool) # not reproducible either
true
Random.AbstractRNGТип
AbstractRNG
Базовый тип для генераторов случайных чисел, таких как MersenneTwister и RandomDevice.
Random.TaskLocalRNGТип
TaskLocalRNG
У TaskLocalRNG состояние локально для задачи, а не для потока. Оно инициализируется при создании задачи из состояния родительской задачи, но без продвижения состояния RNG родителя.
В качестве преимущества, TaskLocalRNG достаточно быстрый и позволяет воспроизводить многопоточные симуляции (за исключением гонок), независимо от решений планировщика. Пока количество потоков не используется для принятия решений о создании задач, результаты моделирования также независимы от количества доступных потоков/ЦП. Поток случайных чисел не должен зависеть от характеристик оборудования, вплоть до порядка байтов и, возможно, размера слова.
Использование или инициализация RNG любой другой задачи, кроме той, что возвращается current_task(), является неопределённым поведением: в большинстве случаев оно будет работать, а в некоторых случаях может потерпеть неудачу без явного сообщения.
Начиная с Julia 1.10, создание задачи больше не продвигает состояние RNG родительской задачи.
Random.XoshiroТип
Xoshiro(seed) Xoshiro()
Xoshiro256++ — это быстрый псевдослучайный генератор, описанный Дэвидом Блэкманом и Себастьяно Вигна в «Scrambled Linear Pseudorandom Number Generators», ACM Trans. Math. Softw., 2021. Реализация справочного материала доступна по адресу http://prng.di.unimi.it
Помимо высокой скорости, Xoshiro обладает небольшим объёмом памяти, что делает его подходящим для приложений, где требуется хранить множество различных случайных состояний в течение длительного времени.
Реализация Xoshiro в Julia имеет режим массового генерации; это позволяет запрашивать новые виртуальные генераторы PRNG от родительского и использовать SIMD для параллельного генерации (т.е. массовый поток состоит из нескольких переплетённых экземпляров xoshiro). Виртуальные генераторы PRNG отбрасываются после обслуживания запроса по массе (и не должны создавать выделения памяти).
Примеры
julia> using Random
julia> rng = Xoshiro(1234);
julia> x1 = rand(rng, 2)
2-element Vector{Float64}:
0.32597672886359486
0.5490511363155669
julia> rng = Xoshiro(1234);
julia> x2 = rand(rng, 2)
2-element Vector{Float64}:
0.32597672886359486
0.5490511363155669
julia> x1 == x2
true
Random.MersenneTwisterТип
MersenneTwister(seed) MersenneTwister()
Создаёт объект MersenneTwister RNG. Разные объекты RNG могут иметь свои собственные зерна, что может быть полезно для генерации разных потоков случайных чисел. seed может быть целым неотрицательным числом или вектором целых чисел UInt32. Если зерно не указано, генерируется случайное зерно (используя энтропию из системы). См. функцию seed! для повторной инициализации существующего объекта MersenneTwister.
Примеры
julia> rng = MersenneTwister(1234);
julia> x1 = rand(rng, 2)
2-element Vector{Float64}:
0.5908446386657102
0.7667970365022592
julia> rng = MersenneTwister(1234);
julia> x2 = rand(rng, 2)
2-element Vector{Float64}:
0.5908446386657102
0.7667970365022592
julia> x1 == x2
true
Random.RandomDeviceТип
RandomDevice()
Создаёт объект RandomDevice RNG. Два таких объекта всегда будут генерировать разные потоки случайных чисел. Энтропия извлекается из операционной системы.
Включение в API Random
Существует два преимущественно ортогональных способа расширения функциональности Random:
- генерирование случайных значений пользовательских типов
- создание новых генераторов
API для 1) достаточно функциональный, но относительно новый, поэтому в последующих версиях модуля Random он может эволюционировать. Например, обычно достаточно реализовать один метод rand для работы всех других обычных методов автоматически.
API для 2) всё ещё является рудиментарным и может потребовать больше усилий от реализатора, чтобы поддержать обычные типы генерируемых значений.
Генерация случайных значений пользовательских типов
Генерация случайных значений для некоторых распределений может предполагать различные компромиссы. Предвычисленные значения, такие как таблица алиасов для дискретных распределений или функции «сжатия» для одномерных распределений, могут значительно ускорить выборку. Сколько информации должно быть предварительно вычислено, может зависеть от количества значений, которые мы планируем получить из распределения. Также некоторые генераторы случайных чисел могут обладать определёнными свойствами, которые могут быть полезны различным алгоритмам.
Модуль Random определяет настраиваемый фреймворк для получения случайных значений, который может решить эти проблемы. Каждый вызов rand генерирует сэмплер, который можно настроить с учётом вышеуказанных компромиссов, добавив методы в Sampler, которые, в свою очередь, могут передавать вызовы генератору случайных чисел, объекту, характеризующему распределение, и предложению количества повторений. В настоящее время для последнего используются Val{1} (для одного образца) и Val{Inf} (для произвольного количества), при этом Random.Repetition является псевдонимом для обоих.
Затем объект, возвращаемый Sampler, используется для генерации случайных значений. При реализации интерфейса случайной генерации для значения X, которое можно получить, реализатор должен определить метод
rand(rng, sampler)
для конкретного sampler возвращаемого Sampler(rng, X, repetition).
Сэмплеры могут быть произвольными значениями, которые реализуют rand(rng, sampler), но для большинства приложений могут быть достаточны следующие предопределённые сэмплеры:
SamplerType{T}()может использоваться для реализации сэмплеров, которые берут значения из типаT(например,rand(Int)). Это значение по умолчанию возвращаетсяSamplerдля типов.SamplerTrivial(self)является простым оболочкой дляself, к которой можно получить доступ с помощью[]. Это рекомендуемый сэмплер, когда не требуется предварительно вычисленная информация (например,rand(1:3)). Это значение по умолчанию возвращаетсяSamplerдля значений.SamplerSimple(self, data)также содержит дополнительное полеdata, которое можно использовать для хранения произвольных предварительно вычисленных значений, которые должны быть вычислены в специальном методеSampler.
Мы предоставляем примеры для каждого из них. Мы предполагаем здесь, что выбор алгоритма не зависит от генератора случайных чисел, поэтому в наших сигнатурах используется AbstractRNG.
Random.SamplerТип
Sampler(rng, x, repetition = Val(Inf))
Возвращает объект сэмплера, который можно использовать для генерации случайных значений из rng для x.
Когда sp = Sampler(rng, x, repetition), rand(rng, sp) будет использоваться для генерации случайных значений и должно быть определено соответствующим образом.
repetition может быть Val(1) или Val(Inf), и должен использоваться в качестве рекомендации для принятия решения о количестве предварительных вычислений, если это применимо.
Random.SamplerType и Random.SamplerTrivial являются значениями по умолчанию для типов и значений соответственно. Random.SamplerSimple можно использовать для хранения предварительно вычисленных значений без определения дополнительных типов только для этой цели.
Random.SamplerTypeТип
SamplerType{T}()
Сэмплер для типов, не содержащий никакой другой информации. Значение по умолчанию для Sampler при вызове с типами.
Random.SamplerTrivialТип
SamplerTrivial(x)
Создаёт сэмплер, который просто оборачивает данное значение x. Это значение по умолчанию для значений. Значение eltype этого сэмплера равно eltype(x).
Рекомендуемый случай использования - выбор значений без предварительно вычисленных данных.
Random.SamplerSimpleТип
SamplerSimple(x, data)
Создаёт сэмплер, который оборачивает данное значение x и data. Значение eltype этого сэмплера равно eltype(x).
Рекомендуемый случай использования - выбор значений с предварительно вычисленными данными.
Разделение предварительных вычислений от фактического генерации значений является частью API и также доступно пользователю. В качестве примера, предположим, что rand(rng, 1:20) должен вызываться многократно в цикле: способ воспользоваться этим разделением следующий:
rng = MersenneTwister()
sp = Random.Sampler(rng, 1:20) # or Random.Sampler(MersenneTwister, 1:20)
for x in X
n = rand(rng, sp) # similar to n = rand(rng, 1:20)
# use n
end
Этот механизм также используется в стандартной библиотеке, например, в реализации генерации случайного массива (как в rand(1:20, 10)).
Генерация значений из типа
Для типа T, в настоящее время предполагается, что если rand(T) определен, будет создан объект типа T . SamplerType является сэмплером по умолчанию для типов. Для определения случайной генерации значений типа T, метод rand(rng::AbstractRNG, ::Random.SamplerType{T}) должен быть определен и должен возвращать значения, которые rand(rng, T) ожидается вернуть.
Рассмотрим следующий пример: мы реализуем тип Die, с переменным числом n граней, пронумерованных от 1 до n . Мы хотим, чтобы rand(Die) генерировал Die со случайным числом до 20 граней (и не менее 4):
struct Die
nsides::Int # number of sides
end
Random.rand(rng::AbstractRNG, ::Random.SamplerType{Die}) = Die(rand(rng, 4:20))
# output
Скалярные и массивно-ориентированные методы для Die теперь работают как ожидается:
julia> rand(Die)
Die(5)
julia> rand(MersenneTwister(0), Die)
Die(11)
julia> rand(Die, 3)
3-element Vector{Die}:
Die(9)
Die(15)
Die(14)
julia> a = Vector{Die}(undef, 3); rand!(a)
3-element Vector{Die}:
Die(19)
Die(7)
Die(17)
Простой сэмплер без предварительно вычисленных данных
Здесь мы определяем сэмплер для коллекции. Если предварительно вычисленные данные не требуются, его можно реализовать с помощью сэмплера SamplerTrivial, который фактически является значением по умолчанию для значений.
Для определения генерации случайных значений из объектов типа S, должен быть определён метод rand(rng::AbstractRNG, sp::Random.SamplerTrivial{S}). Здесь sp просто оборачивает объект типа S, к которому можно получить доступ через sp[]. Продолжая пример Die, мы теперь хотим определить rand(d::Die) для генерации Int, соответствующего одной из граней d.
julia> Random.rand(rng::AbstractRNG, d::Random.SamplerTrivial{Die}) = rand(rng, 1:d[].nsides);
julia> rand(Die(4))
1
julia> rand(Die(4), 3)
3-element Vector{Any}:
2
3
3
Для типа коллекции S, в настоящее время предполагается, что если rand(::S) определен, будет создан объект типа eltype(S) . В последнем примере генерируется Vector{Any}; причина в том, что eltype(Die) == Any. Решением является определение Base.eltype(::Type{Die}) = Int.
Генерация значений для типа AbstractFloat
Типы AbstractFloat являются специальными случаями, потому что по умолчанию случайные значения не генерируются в области всего типа, а в [0,1). Следующий метод должен быть реализован для T <: AbstractFloat: Random.rand(::AbstractRNG, ::Random.SamplerTrivial{Random.CloseOpen01{T}})
Оптимизированный сэмплер с предварительно вычисленными данными
Рассмотрим дискретное распределение, где числа 1:n выбираются с заданными вероятностями, которые суммируются до единицы. Когда требуется много значений из этого распределения, самый быстрый метод - использование таблицы алиасов. Мы не будем здесь описывать алгоритм построения такой таблицы, но предположим, что она доступна в make_alias_table(probabilities), и draw_number(rng, alias_table) можно использовать для выбора случайного числа из неё.
Предположим, что распределение описывается
struct DiscreteDistribution{V <: AbstractVector}
probabilities::V
end
и что мы всегда хотим строить таблицу алиасов, независимо от количества необходимых значений (мы узнаем, как настроить это ниже). Методы
Random.eltype(::Type{<:DiscreteDistribution}) = Int
function Random.Sampler(::Type{<:AbstractRNG}, distribution::DiscreteDistribution, ::Repetition)
SamplerSimple(disribution, make_alias_table(distribution.probabilities))
end
должны быть определены для возвращения сэмплера с предварительно вычисленными данными, а затем
function rand(rng::AbstractRNG, sp::SamplerSimple{<:DiscreteDistribution})
draw_number(rng, sp.data)
end
будут использоваться для выбора значений.
Пользовательские типы сэмплеров
Тип SamplerSimple достаточен для большинства случаев использования с предварительно вычисленными данными. Однако, чтобы продемонстрировать, как использовать пользовательские типы сэмплеров, здесь мы реализуем что-то подобное SamplerSimple.
Вернёмся к нашему примеру Die: rand(::Die) использует генерацию случайных чисел из диапазона, поэтому есть возможность этой оптимизации. Мы называем наш пользовательский сэмплер SamplerDie.
import Random: Sampler, rand
struct SamplerDie <: Sampler{Int} # generates values of type Int
die::Die
sp::Sampler{Int} # this is an abstract type, so this could be improved
end
Sampler(RNG::Type{<:AbstractRNG}, die::Die, r::Random.Repetition) =
SamplerDie(die, Sampler(RNG, 1:die.nsides, r))
# the `r` parameter will be explained later on
rand(rng::AbstractRNG, sp::SamplerDie) = rand(rng, sp.sp)
Теперь можно получить сэмплер с sp = Sampler(rng, die), и использовать sp вместо die в любом вызове rand с rng. В упрощённом примере выше, die не нужно хранить в SamplerDie, но это часто встречается на практике.
Конечно, этот шаблон настолько часто используется, что вспомогательный тип, используемый выше, а именно Random.SamplerSimple, доступен, экономя нам определение SamplerDie: мы могли бы реализовать наше разделение с помощью:
Sampler(RNG::Type{<:AbstractRNG}, die::Die, r::Random.Repetition) =
SamplerSimple(die, Sampler(RNG, 1:die.nsides, r))
rand(rng::AbstractRNG, sp::SamplerSimple{Die}) = rand(rng, sp.data)
Здесь sp.data относится ко второму параметру вызова конструктора SamplerSimple (в данном случае равна Sampler(rng, 1:die.nsides, r) ), в то время как объект Die можно получить через sp[].
Как и SamplerDie, любой пользовательский сэмплер должен быть подтипом Sampler{T} , где T является типом генерируемых значений. Обратите внимание, что SamplerSimple(x, data) isa Sampler{eltype(x)}, поэтому это ограничивает то, что может быть первым аргументом SamplerSimple (рекомендуется использовать SamplerSimple как в примере Die , где x просто передаётся при определении метода Sampler ). Аналогично, SamplerTrivial(x) isa Sampler{eltype(x)}.
Другой вспомогательный тип в настоящее время доступен для других случаев, Random.SamplerTag, но рассматривается как внутреннее API и может сломаться в любое время без надлежащих предупреждений.
Использование различных алгоритмов для генерации скалярных или массивов
В некоторых случаях выбор алгоритма зависит от того, нужно ли сгенерировать несколько значений или большое количество. Это обрабатывается третьим параметром конструктора Sampler . Предположим, мы определили два вспомогательных типа для Die, скажем, SamplerDie1, который должен использоваться для генерации небольшого количества случайных значений, и SamplerDieMany для многих значений. Мы можем использовать эти типы следующим образом:
Sampler(RNG::Type{<:AbstractRNG}, die::Die, ::Val{1}) = SamplerDie1(...)
Sampler(RNG::Type{<:AbstractRNG}, die::Die, ::Val{Inf}) = SamplerDieMany(...)
Конечно, rand также должен быть определен для этих типов (т. е. rand(::AbstractRNG, ::SamplerDie1) и rand(::AbstractRNG, ::SamplerDieMany)). Обратите внимание, что, как обычно, можно использовать SamplerTrivial и SamplerSimple, если пользовательские типы не требуются.
Примечание: Sampler(rng, x) — это просто сокращение для Sampler(rng, x, Val(Inf)), а Random.Repetition — псевдоним для Union{Val{1}, Val{Inf}}.
Создание новых генераторов
API пока не определен чётко, но в качестве правила:
- любой
randметод, генерирующий "базовые" типы (isbitstypeцелые и плавающие типы вBase) должен быть определен для этого конкретного генератора случайных чисел, если они нужны; - другие документированные
randметоды, принимающиеAbstractRNG, должны работать прямо из коробки (при условии, что реализованы методы из п. 1), на которые они опираются), но, конечно, могут быть специализированы для этого генератора, если есть возможность оптимизации; -
copyдля псевдогенераторов должен возвращать независимую копию, которая генерирует точно такую же последовательность случайных чисел, что и оригинал, начиная с того момента, когда вызывается аналогичным образом. Если это не осуществимо (например, генераторы случайных чисел на основе оборудования),copyне должен быть реализован.
Что касается п. 1), метод rand может работать автоматически, но это не поддерживается официально и может сломаться без предупреждений в последующей версии.
Для определения нового rand метода для гипотетического генератора MyRNG и спецификации значения s (например, s == Int, или s == 1:10 ) типа S==typeof(s) или S==Type{s} , если s является типом, должны быть определены те же два метода, что и прежде:
-
Sampler(::Type{MyRNG}, ::S, ::Repetition), который возвращает объект типа, скажем,SamplerS rand(rng::MyRNG, sp::SamplerS)
Может случиться, что Sampler(rng::AbstractRNG, ::S, ::Repetition) уже определен в модуле Random. В этом случае можно пропустить шаг 1) на практике (если нужно специализировать генерацию для этого конкретного типа генератора случайных чисел), но соответствующий тип SamplerS рассматривается как внутренняя деталь и может быть изменён без предупреждения.
Специализация генерации массивов
В некоторых случаях для заданного типа генератора случайных чисел генерация массива случайных значений может быть более эффективной с помощью специализированного метода, чем просто использование техники декомпозиции, описанной ранее. Например, это относится к MersenneTwister, который напрямую записывает случайные значения в массив.
Для реализации этой специализации для MyRNG и спецификации s, производящей элементы типа S, может быть определён следующий метод: rand!(rng::MyRNG, a::AbstractArray{S}, ::SamplerS), где SamplerS — тип возвращаемого выборщика Sampler(MyRNG, s, Val(Inf)). Вместо AbstractArray, можно реализовать функциональность только для подтипа, например, Array{S}. Неизменяющий метод массива rand будет автоматически вызывать эту специализацию внутри.
Воспроизводимость
Используя параметр генератора случайных чисел, инициализированный заданным значением, вы можете воспроизвести ту же последовательность псевдослучайных чисел при многократном запуске программы. Однако незначительное обновление Julia (например, с 1.3 до 1.4) может изменить последовательность псевдослучайных чисел, сгенерированных из определённого значения, особенно если используется MersenneTwister. (Даже если последовательность, созданная низкоуровневой функцией, такой как rand, не меняется, вывод высокоуровневых функций, таких как randsubseq, может измениться из-за обновлений алгоритма.) Обоснование: гарантирование неизменности потоков псевдослучайных чисел препятствует многим алгоритмическим усовершенствованиям.
Если вам нужно гарантировать точную воспроизводимость данных случайности, рекомендуется просто сохранить данные (например, в качестве дополнительного приложения в научной публикации). (Вы также можете, конечно, указать конкретную версию Julia и манифест пакета, особенно если требуется битовая воспроизводимость.)
Тесты программного обеспечения, которые полагаются на конкретные "случайные" данные, также, как правило, должны либо сохранять данные, встраивать их в код теста, либо использовать сторонние пакеты, такие как StableRNGs.jl. С другой стороны, тесты, которые должны проходить для большинства случайных данных (например, тестирование A \ (A*x) ≈ x для случайной матрицы A = randn(n,n) ) могут использовать генератор случайных чисел с фиксированным значением для обеспечения того, что простой многократный запуск теста не столкнётся с ошибкой из-за очень маловероятных данных (например, чрезвычайно плохо обусловленной матрицы).
Статистическое распределение, из которого извлекаются случайные выборки, гарантированно одинаково во всех незначительных обновлениях Julia.
© 2009–2024 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors
Licensed under the MIT License.
https://docs.julialang.org/en/v1.10/stdlib/Random/